Merge "Make cfn functions inherit from HOT"
This commit is contained in:
commit
54feba728b
|
@ -12,7 +12,6 @@
|
|||
# under the License.
|
||||
|
||||
import collections
|
||||
import itertools
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
|
@ -21,6 +20,7 @@ from heat.api.aws import utils as aws_utils
|
|||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import function
|
||||
from heat.engine.hot import functions as hot_funcs
|
||||
|
||||
|
||||
class FindInMap(function.Function):
|
||||
|
@ -88,31 +88,6 @@ class ParamRef(function.Function):
|
|||
key='unknown')
|
||||
|
||||
|
||||
class ResourceRef(function.Function):
|
||||
"""A function for resolving resource references.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "Ref" : "<resource_name>" }
|
||||
"""
|
||||
|
||||
def _resource(self, path='unknown'):
|
||||
resource_name = function.resolve(self.args)
|
||||
|
||||
try:
|
||||
return self.stack[resource_name]
|
||||
except KeyError:
|
||||
raise exception.InvalidTemplateReference(resource=resource_name,
|
||||
key=path)
|
||||
|
||||
def dependencies(self, path):
|
||||
return itertools.chain(super(ResourceRef, self).dependencies(path),
|
||||
[self._resource(path)])
|
||||
|
||||
def result(self):
|
||||
return self._resource().FnGetRefId()
|
||||
|
||||
|
||||
def Ref(stack, fn_name, args):
|
||||
"""A function for resolving parameters or resource references.
|
||||
|
||||
|
@ -125,26 +100,21 @@ def Ref(stack, fn_name, args):
|
|||
{ "Ref" : "<resource_name>" }
|
||||
"""
|
||||
if args in stack:
|
||||
RefClass = ResourceRef
|
||||
RefClass = hot_funcs.GetResource
|
||||
else:
|
||||
RefClass = ParamRef
|
||||
return RefClass(stack, fn_name, args)
|
||||
|
||||
|
||||
class GetAtt(function.Function):
|
||||
class GetAtt(hot_funcs.GetAttThenSelect):
|
||||
"""A function for resolving resource attributes.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "Fn::GetAtt" : [ "<resource_name>",
|
||||
"<attribute_name" ] }
|
||||
"<attribute_name>" ] }
|
||||
"""
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(GetAtt, self).__init__(stack, fn_name, args)
|
||||
|
||||
self._resource_name, self._attribute = self._parse_args()
|
||||
|
||||
def _parse_args(self):
|
||||
try:
|
||||
resource_name, attribute = self.args
|
||||
|
@ -152,62 +122,7 @@ class GetAtt(function.Function):
|
|||
raise ValueError(_('Arguments to "%s" must be of the form '
|
||||
'[resource_name, attribute]') % self.fn_name)
|
||||
|
||||
return resource_name, attribute
|
||||
|
||||
def _resource(self, path='unknown'):
|
||||
resource_name = function.resolve(self._resource_name)
|
||||
|
||||
try:
|
||||
return self.stack[resource_name]
|
||||
except KeyError:
|
||||
raise exception.InvalidTemplateReference(resource=resource_name,
|
||||
key=path)
|
||||
|
||||
def dep_attrs(self, resource_name):
|
||||
if self._resource().name == resource_name:
|
||||
attrs = [function.resolve(self._attribute)]
|
||||
else:
|
||||
attrs = []
|
||||
return itertools.chain(super(GetAtt, self).dep_attrs(resource_name),
|
||||
attrs)
|
||||
|
||||
def dependencies(self, path):
|
||||
return itertools.chain(super(GetAtt, self).dependencies(path),
|
||||
[self._resource(path)])
|
||||
|
||||
def _allow_without_attribute_name(self):
|
||||
return False
|
||||
|
||||
def validate(self):
|
||||
super(GetAtt, self).validate()
|
||||
res = self._resource()
|
||||
|
||||
if self._allow_without_attribute_name():
|
||||
# if allow without attribute_name, then don't check
|
||||
# when attribute_name is None
|
||||
if self._attribute is None:
|
||||
return
|
||||
|
||||
attr = function.resolve(self._attribute)
|
||||
from heat.engine import resource
|
||||
if (type(res).get_attribute == resource.Resource.get_attribute and
|
||||
attr not in res.attributes_schema):
|
||||
raise exception.InvalidTemplateAttribute(
|
||||
resource=self._resource_name, key=attr)
|
||||
|
||||
def result(self):
|
||||
attribute = function.resolve(self._attribute)
|
||||
|
||||
r = self._resource()
|
||||
if r.action in (r.CREATE, r.ADOPT, r.SUSPEND, r.RESUME,
|
||||
r.UPDATE, r.ROLLBACK, r.SNAPSHOT, r.CHECK):
|
||||
return r.FnGetAtt(attribute)
|
||||
# NOTE(sirushtim): Add r.INIT to states above once convergence
|
||||
# is the default.
|
||||
elif r.stack.has_cache_data(r.name) and r.action == r.INIT:
|
||||
return r.FnGetAtt(attribute)
|
||||
else:
|
||||
return None
|
||||
return resource_name, attribute, []
|
||||
|
||||
|
||||
class Select(function.Function):
|
||||
|
@ -217,7 +132,7 @@ class Select(function.Function):
|
|||
|
||||
{ "Fn::Select" : [ "<index>", [ "<value_1>", "<value_2>", ... ] ] }
|
||||
|
||||
Takes the form (for a map lookup)::
|
||||
or (for a map lookup)::
|
||||
|
||||
{ "Fn::Select" : [ "<index>", { "<key_1>": "<value_1>", ... } ] }
|
||||
|
||||
|
@ -283,7 +198,7 @@ class Select(function.Function):
|
|||
self.fn_name)
|
||||
|
||||
|
||||
class Join(function.Function):
|
||||
class Join(hot_funcs.Join):
|
||||
"""A function for joining strings.
|
||||
|
||||
Takes the form::
|
||||
|
@ -295,47 +210,6 @@ class Join(function.Function):
|
|||
"<string_1><delim><string_2><delim>..."
|
||||
"""
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(Join, self).__init__(stack, fn_name, args)
|
||||
|
||||
example = '"%s" : [ " ", [ "str1", "str2"]]' % self.fn_name
|
||||
fmt_data = {'fn_name': self.fn_name,
|
||||
'example': example}
|
||||
|
||||
if not isinstance(self.args, list):
|
||||
raise TypeError(_('Incorrect arguments to "%(fn_name)s" '
|
||||
'should be: %(example)s') % fmt_data)
|
||||
|
||||
try:
|
||||
self._delim, self._strings = self.args
|
||||
except ValueError:
|
||||
raise ValueError(_('Incorrect arguments to "%(fn_name)s" '
|
||||
'should be: %(example)s') % fmt_data)
|
||||
|
||||
def result(self):
|
||||
strings = function.resolve(self._strings)
|
||||
if strings is None:
|
||||
strings = []
|
||||
if (isinstance(strings, six.string_types) or
|
||||
not isinstance(strings, collections.Sequence)):
|
||||
raise TypeError(_('"%s" must operate on a list') % self.fn_name)
|
||||
|
||||
delim = function.resolve(self._delim)
|
||||
if not isinstance(delim, six.string_types):
|
||||
raise TypeError(_('"%s" delimiter must be a string') %
|
||||
self.fn_name)
|
||||
|
||||
def ensure_string(s):
|
||||
if s is None:
|
||||
return ''
|
||||
if not isinstance(s, six.string_types):
|
||||
raise TypeError(
|
||||
_('Items to join must be strings not %s'
|
||||
) % (repr(s)[:200]))
|
||||
return s
|
||||
|
||||
return delim.join(ensure_string(s) for s in strings)
|
||||
|
||||
|
||||
class Split(function.Function):
|
||||
"""A function for splitting strings.
|
||||
|
@ -379,7 +253,7 @@ class Split(function.Function):
|
|||
return strings.split(self._delim)
|
||||
|
||||
|
||||
class Replace(function.Function):
|
||||
class Replace(hot_funcs.Replace):
|
||||
"""A function for performing string substitutions.
|
||||
|
||||
Takes the form::
|
||||
|
@ -398,15 +272,6 @@ class Replace(function.Function):
|
|||
performed is otherwise undefined.
|
||||
"""
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(Replace, self).__init__(stack, fn_name, args)
|
||||
|
||||
self._mapping, self._string = self._parse_args()
|
||||
if not isinstance(self._mapping,
|
||||
(collections.Mapping, function.Function)):
|
||||
raise TypeError(_('"%s" parameters must be a mapping') %
|
||||
self.fn_name)
|
||||
|
||||
def _parse_args(self):
|
||||
|
||||
example = ('{"%s": '
|
||||
|
@ -427,39 +292,6 @@ class Replace(function.Function):
|
|||
else:
|
||||
return mapping, string
|
||||
|
||||
def result(self):
|
||||
template = function.resolve(self._string)
|
||||
mapping = function.resolve(self._mapping)
|
||||
|
||||
if not isinstance(template, six.string_types):
|
||||
raise TypeError(_('"%s" template must be a string') % self.fn_name)
|
||||
|
||||
if not isinstance(mapping, collections.Mapping):
|
||||
raise TypeError(_('"%s" params must be a map') % self.fn_name)
|
||||
|
||||
def replace(string, change):
|
||||
placeholder, value = change
|
||||
|
||||
if not isinstance(placeholder, six.string_types):
|
||||
raise TypeError(_('"%s" param placeholders must be strings') %
|
||||
self.fn_name)
|
||||
|
||||
if value is None:
|
||||
value = ''
|
||||
|
||||
if not isinstance(value,
|
||||
(six.string_types, six.integer_types,
|
||||
float, bool)):
|
||||
raise TypeError(_('"%s" params must be strings or numbers') %
|
||||
self.fn_name)
|
||||
|
||||
return string.replace(placeholder, six.text_type(value))
|
||||
|
||||
mapping = collections.OrderedDict(sorted(mapping.items(),
|
||||
key=lambda t: len(t[0]),
|
||||
reverse=True))
|
||||
return six.moves.reduce(replace, six.iteritems(mapping), template)
|
||||
|
||||
|
||||
class Base64(function.Function):
|
||||
"""A placeholder function for converting to base64.
|
||||
|
@ -535,7 +367,7 @@ class MemberListToMap(function.Function):
|
|||
valuename=self._valuename)
|
||||
|
||||
|
||||
class ResourceFacade(function.Function):
|
||||
class ResourceFacade(hot_funcs.ResourceFacade):
|
||||
"""A function for retrieving data in a parent provider template.
|
||||
|
||||
A function for obtaining data from the facade resource from within the
|
||||
|
@ -555,40 +387,46 @@ class ResourceFacade(function.Function):
|
|||
'Metadata', 'DeletionPolicy', 'UpdatePolicy'
|
||||
)
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(ResourceFacade, self).__init__(stack, fn_name, args)
|
||||
|
||||
if self.args not in self._RESOURCE_ATTRIBUTES:
|
||||
fmt_data = {'fn_name': self.fn_name,
|
||||
'allowed': ', '.join(self._RESOURCE_ATTRIBUTES)}
|
||||
raise ValueError(_('Incorrect arguments to "%(fn_name)s" '
|
||||
'should be one of: %(allowed)s') % fmt_data)
|
||||
|
||||
def result(self):
|
||||
attr = function.resolve(self.args)
|
||||
|
||||
if attr == self.METADATA:
|
||||
return self.stack.parent_resource.metadata_get()
|
||||
elif attr == self.UPDATE_POLICY:
|
||||
up = self.stack.parent_resource.t._update_policy or {}
|
||||
return function.resolve(up)
|
||||
elif attr == self.DELETION_POLICY:
|
||||
return self.stack.parent_resource.t.deletion_policy()
|
||||
|
||||
|
||||
class Not(function.Function):
|
||||
"""A function acts as a NOT operator.
|
||||
class If(hot_funcs.If):
|
||||
"""A function to return corresponding value based on condition evaluation.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "Fn::Not" : [condition] }
|
||||
{ "Fn::If" : [ "<condition_name>",
|
||||
"<value_if_true>",
|
||||
"<value_if_false>" ] }
|
||||
|
||||
The value_if_true to be returned if the specified condition evaluates
|
||||
to true, the value_if_false to be returned if the specified condition
|
||||
evaluates to false.
|
||||
"""
|
||||
|
||||
|
||||
class Equals(hot_funcs.Equals):
|
||||
"""A function for comparing whether two values are equal.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "Fn::Equals" : [ "<value_1>", "<value_2>" ] }
|
||||
|
||||
The value can be any type that you want to compare. Returns true
|
||||
if the two values are equal or false if they aren't.
|
||||
"""
|
||||
|
||||
|
||||
class Not(hot_funcs.Not):
|
||||
"""A function that acts as a NOT operator on a condition.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "Fn::Not" : [ "<condition>" ] }
|
||||
|
||||
Returns true for a condition that evaluates to false or
|
||||
returns false for a condition that evaluates to true.
|
||||
"""
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(Not, self).__init__(stack, fn_name, args)
|
||||
def _get_condition(self):
|
||||
try:
|
||||
if (not self.args or
|
||||
not isinstance(self.args, collections.Sequence) or
|
||||
|
@ -596,7 +434,7 @@ class Not(function.Function):
|
|||
raise ValueError()
|
||||
if len(self.args) != 1:
|
||||
raise ValueError()
|
||||
self.condition = self.args[0]
|
||||
return self.args[0]
|
||||
except ValueError:
|
||||
msg = _('Arguments to "%s" must be of the form: '
|
||||
'[condition]')
|
||||
|
@ -609,3 +447,29 @@ class Not(function.Function):
|
|||
'after resolved the value is: %s')
|
||||
raise ValueError(msg % resolved_value)
|
||||
return not resolved_value
|
||||
|
||||
|
||||
class And(hot_funcs.And):
|
||||
"""A function that acts as an AND operator on conditions.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "Fn::And" : [ "<condition_1>", "<condition_2>", ... ] }
|
||||
|
||||
Returns true if all the specified conditions evaluate to true, or returns
|
||||
false if any one of the conditions evaluates to false. The minimum number
|
||||
of conditions that you can include is 2.
|
||||
"""
|
||||
|
||||
|
||||
class Or(hot_funcs.Or):
|
||||
"""A function that acts as an OR operator on conditions.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "Fn::Or" : [ "<condition_1>", "<condition_2>", ... ] }
|
||||
|
||||
Returns true if any one of the specified conditions evaluate to true,
|
||||
or returns false if all of the conditions evaluates to false. The minimum
|
||||
number of conditions that you can include is 2.
|
||||
"""
|
||||
|
|
|
@ -17,7 +17,6 @@ from heat.common import exception
|
|||
from heat.common.i18n import _
|
||||
from heat.engine.cfn import functions as cfn_funcs
|
||||
from heat.engine import function
|
||||
from heat.engine.hot import functions as hot_funcs
|
||||
from heat.engine import parameters
|
||||
from heat.engine import rsrc_defn
|
||||
from heat.engine import template_common
|
||||
|
@ -212,16 +211,16 @@ class CfnTemplate(CfnTemplateBase):
|
|||
'Fn::Base64': cfn_funcs.Base64,
|
||||
'Fn::MemberListToMap': cfn_funcs.MemberListToMap,
|
||||
'Fn::ResourceFacade': cfn_funcs.ResourceFacade,
|
||||
'Fn::If': hot_funcs.If,
|
||||
'Fn::If': cfn_funcs.If,
|
||||
}
|
||||
|
||||
condition_functions = {
|
||||
'Fn::Equals': hot_funcs.Equals,
|
||||
'Fn::Equals': cfn_funcs.Equals,
|
||||
'Ref': cfn_funcs.ParamRef,
|
||||
'Fn::FindInMap': cfn_funcs.FindInMap,
|
||||
'Fn::Not': cfn_funcs.Not,
|
||||
'Fn::And': hot_funcs.And,
|
||||
'Fn::Or': hot_funcs.Or
|
||||
'Fn::And': cfn_funcs.And,
|
||||
'Fn::Or': cfn_funcs.Or
|
||||
}
|
||||
|
||||
def __init__(self, tmpl, template_id=None, files=None, env=None):
|
||||
|
|
|
@ -24,7 +24,6 @@ from yaql.language import exceptions
|
|||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import attributes
|
||||
from heat.engine.cfn import functions as cfn_funcs
|
||||
from heat.engine import function
|
||||
|
||||
opts = [
|
||||
|
@ -113,7 +112,32 @@ class GetParam(function.Function):
|
|||
return ''
|
||||
|
||||
|
||||
class GetAttThenSelect(cfn_funcs.GetAtt):
|
||||
class GetResource(function.Function):
|
||||
"""A function for resolving resource references.
|
||||
|
||||
Takes the form::
|
||||
|
||||
get_resource: <resource_name>
|
||||
"""
|
||||
|
||||
def _resource(self, path='unknown'):
|
||||
resource_name = function.resolve(self.args)
|
||||
|
||||
try:
|
||||
return self.stack[resource_name]
|
||||
except KeyError:
|
||||
raise exception.InvalidTemplateReference(resource=resource_name,
|
||||
key=path)
|
||||
|
||||
def dependencies(self, path):
|
||||
return itertools.chain(super(GetResource, self).dependencies(path),
|
||||
[self._resource(path)])
|
||||
|
||||
def result(self):
|
||||
return self._resource().FnGetRefId()
|
||||
|
||||
|
||||
class GetAttThenSelect(function.Function):
|
||||
"""A function for resolving resource attributes.
|
||||
|
||||
Takes the form::
|
||||
|
@ -125,6 +149,13 @@ class GetAttThenSelect(cfn_funcs.GetAtt):
|
|||
- ...
|
||||
"""
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(GetAttThenSelect, self).__init__(stack, fn_name, args)
|
||||
|
||||
(self._resource_name,
|
||||
self._attribute,
|
||||
self._path_components) = self._parse_args()
|
||||
|
||||
def _parse_args(self):
|
||||
if (not isinstance(self.args, collections.Sequence) or
|
||||
isinstance(self.args, six.string_types)):
|
||||
|
@ -136,12 +167,65 @@ class GetAttThenSelect(cfn_funcs.GetAtt):
|
|||
'[resource_name, attribute, (path), ...]') %
|
||||
self.fn_name)
|
||||
|
||||
self._path_components = self.args[2:]
|
||||
return self.args[0], self.args[1], self.args[2:]
|
||||
|
||||
return tuple(self.args[:2])
|
||||
def _resource(self, path='unknown'):
|
||||
resource_name = function.resolve(self._resource_name)
|
||||
|
||||
try:
|
||||
return self.stack[resource_name]
|
||||
except KeyError:
|
||||
raise exception.InvalidTemplateReference(resource=resource_name,
|
||||
key=path)
|
||||
|
||||
def dep_attrs(self, resource_name):
|
||||
if self._resource().name == resource_name:
|
||||
attrs = [function.resolve(self._attribute)]
|
||||
else:
|
||||
attrs = []
|
||||
return itertools.chain(super(GetAttThenSelect,
|
||||
self).dep_attrs(resource_name),
|
||||
attrs)
|
||||
|
||||
def dependencies(self, path):
|
||||
return itertools.chain(super(GetAttThenSelect,
|
||||
self).dependencies(path),
|
||||
[self._resource(path)])
|
||||
|
||||
def _allow_without_attribute_name(self):
|
||||
return False
|
||||
|
||||
def validate(self):
|
||||
super(GetAttThenSelect, self).validate()
|
||||
res = self._resource()
|
||||
|
||||
if self._allow_without_attribute_name():
|
||||
# if allow without attribute_name, then don't check
|
||||
# when attribute_name is None
|
||||
if self._attribute is None:
|
||||
return
|
||||
|
||||
attr = function.resolve(self._attribute)
|
||||
from heat.engine import resource
|
||||
if (type(res).get_attribute == resource.Resource.get_attribute and
|
||||
attr not in res.attributes_schema):
|
||||
raise exception.InvalidTemplateAttribute(
|
||||
resource=self._resource_name, key=attr)
|
||||
|
||||
def result(self):
|
||||
attribute = super(GetAttThenSelect, self).result()
|
||||
attr_name = function.resolve(self._attribute)
|
||||
|
||||
r = self._resource()
|
||||
if r.action in (r.CREATE, r.ADOPT, r.SUSPEND, r.RESUME,
|
||||
r.UPDATE, r.ROLLBACK, r.SNAPSHOT, r.CHECK):
|
||||
attribute = r.FnGetAtt(attr_name)
|
||||
# NOTE(sirushtim): Add r.INIT to states above once convergence
|
||||
# is the default.
|
||||
elif r.stack.has_cache_data(r.name) and r.action == r.INIT:
|
||||
attribute = r.FnGetAtt(attr_name)
|
||||
else:
|
||||
attribute = None
|
||||
|
||||
if attribute is None:
|
||||
return None
|
||||
|
||||
|
@ -213,7 +297,7 @@ class GetAttAllAttributes(GetAtt):
|
|||
if len(self.args) > 1:
|
||||
return super(GetAttAllAttributes, self)._parse_args()
|
||||
else:
|
||||
return self.args[0], None
|
||||
return self.args[0], None, []
|
||||
else:
|
||||
raise TypeError(_('Argument to "%s" must be a list') %
|
||||
self.fn_name)
|
||||
|
@ -246,7 +330,7 @@ class GetAttAllAttributes(GetAtt):
|
|||
return True
|
||||
|
||||
|
||||
class Replace(cfn_funcs.Replace):
|
||||
class Replace(function.Function):
|
||||
"""A function for performing string substitutions.
|
||||
|
||||
Takes the form::
|
||||
|
@ -267,6 +351,15 @@ class Replace(cfn_funcs.Replace):
|
|||
performed is otherwise undefined.
|
||||
"""
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(Replace, self).__init__(stack, fn_name, args)
|
||||
|
||||
self._mapping, self._string = self._parse_args()
|
||||
if not isinstance(self._mapping,
|
||||
(collections.Mapping, function.Function)):
|
||||
raise TypeError(_('"%s" parameters must be a mapping') %
|
||||
self.fn_name)
|
||||
|
||||
def _parse_args(self):
|
||||
if not isinstance(self.args, collections.Mapping):
|
||||
raise TypeError(_('Arguments to "%s" must be a map') %
|
||||
|
@ -286,6 +379,39 @@ class Replace(cfn_funcs.Replace):
|
|||
else:
|
||||
return mapping, string
|
||||
|
||||
def result(self):
|
||||
template = function.resolve(self._string)
|
||||
mapping = function.resolve(self._mapping)
|
||||
|
||||
if not isinstance(template, six.string_types):
|
||||
raise TypeError(_('"%s" template must be a string') % self.fn_name)
|
||||
|
||||
if not isinstance(mapping, collections.Mapping):
|
||||
raise TypeError(_('"%s" params must be a map') % self.fn_name)
|
||||
|
||||
def replace(string, change):
|
||||
placeholder, value = change
|
||||
|
||||
if not isinstance(placeholder, six.string_types):
|
||||
raise TypeError(_('"%s" param placeholders must be strings') %
|
||||
self.fn_name)
|
||||
|
||||
if value is None:
|
||||
value = ''
|
||||
|
||||
if not isinstance(value,
|
||||
(six.string_types, six.integer_types,
|
||||
float, bool)):
|
||||
raise TypeError(_('"%s" params must be strings or numbers') %
|
||||
self.fn_name)
|
||||
|
||||
return string.replace(placeholder, six.text_type(value))
|
||||
|
||||
mapping = collections.OrderedDict(sorted(mapping.items(),
|
||||
key=lambda t: len(t[0]),
|
||||
reverse=True))
|
||||
return six.moves.reduce(replace, six.iteritems(mapping), template)
|
||||
|
||||
|
||||
class ReplaceJson(Replace):
|
||||
"""A function for performing string substitutions.
|
||||
|
@ -387,25 +513,75 @@ class GetFile(function.Function):
|
|||
return f
|
||||
|
||||
|
||||
class Join(cfn_funcs.Join):
|
||||
class Join(function.Function):
|
||||
"""A function for joining strings.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "list_join" : [ "<delim>", [ "<string_1>", "<string_2>", ... ] ] }
|
||||
list_join:
|
||||
- <delim>
|
||||
- - <string_1>
|
||||
- <string_2>
|
||||
- ...
|
||||
|
||||
And resolves to::
|
||||
|
||||
"<string_1><delim><string_2><delim>..."
|
||||
"""
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(Join, self).__init__(stack, fn_name, args)
|
||||
|
||||
example = '"%s" : [ " ", [ "str1", "str2"]]' % self.fn_name
|
||||
fmt_data = {'fn_name': self.fn_name,
|
||||
'example': example}
|
||||
|
||||
if not isinstance(self.args, list):
|
||||
raise TypeError(_('Incorrect arguments to "%(fn_name)s" '
|
||||
'should be: %(example)s') % fmt_data)
|
||||
|
||||
try:
|
||||
self._delim, self._strings = self.args
|
||||
except ValueError:
|
||||
raise ValueError(_('Incorrect arguments to "%(fn_name)s" '
|
||||
'should be: %(example)s') % fmt_data)
|
||||
|
||||
def result(self):
|
||||
strings = function.resolve(self._strings)
|
||||
if strings is None:
|
||||
strings = []
|
||||
if (isinstance(strings, six.string_types) or
|
||||
not isinstance(strings, collections.Sequence)):
|
||||
raise TypeError(_('"%s" must operate on a list') % self.fn_name)
|
||||
|
||||
delim = function.resolve(self._delim)
|
||||
if not isinstance(delim, six.string_types):
|
||||
raise TypeError(_('"%s" delimiter must be a string') %
|
||||
self.fn_name)
|
||||
|
||||
def ensure_string(s):
|
||||
if s is None:
|
||||
return ''
|
||||
if not isinstance(s, six.string_types):
|
||||
raise TypeError(
|
||||
_('Items to join must be strings not %s'
|
||||
) % (repr(s)[:200]))
|
||||
return s
|
||||
|
||||
return delim.join(ensure_string(s) for s in strings)
|
||||
|
||||
|
||||
class JoinMultiple(function.Function):
|
||||
"""A function for joining one or more lists of strings.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "list_join" : [ "<delim>", [ "<string_1>", "<string_2>", ... ] ] }
|
||||
list_join:
|
||||
- <delim>
|
||||
- - <string_1>
|
||||
- <string_2>
|
||||
- ...
|
||||
- - ...
|
||||
|
||||
And resolves to::
|
||||
|
||||
|
@ -476,11 +652,14 @@ class MapMerge(function.Function):
|
|||
|
||||
Takes the form::
|
||||
|
||||
{ "map_merge" : [{'k1': 'v1', 'k2': 'v2'}, {'k1': 'v2'}] }
|
||||
map_merge:
|
||||
- <k1>: <v1>
|
||||
<k2>: <v2>
|
||||
- <k1>: <v3>
|
||||
|
||||
And resolves to::
|
||||
|
||||
{'k1': 'v2', 'k2': 'v2'}
|
||||
{"<k1>": "<v2>", "<k2>": "<v3>"}
|
||||
|
||||
"""
|
||||
|
||||
|
@ -517,13 +696,17 @@ class MapReplace(function.Function):
|
|||
|
||||
Takes the form::
|
||||
|
||||
{"map_replace" : [{'k1': 'v1', 'k2': 'v2'},
|
||||
{'keys': {'k1': 'K1'},
|
||||
'values': {'v2': 'V2'}}]}
|
||||
map_replace:
|
||||
- <k1>: <v1>
|
||||
<k2>: <v2>
|
||||
- keys:
|
||||
<k1>: <K1>
|
||||
values:
|
||||
<v2>: <V2>
|
||||
|
||||
And resolves to::
|
||||
|
||||
{'K1': 'v1', 'k2': 'V2'}
|
||||
{"<K1>": "<v1>", "<k2>": "<V2>"}
|
||||
|
||||
"""
|
||||
|
||||
|
@ -588,7 +771,7 @@ class MapReplace(function.Function):
|
|||
return ret_map
|
||||
|
||||
|
||||
class ResourceFacade(cfn_funcs.ResourceFacade):
|
||||
class ResourceFacade(function.Function):
|
||||
"""A function for retrieving data in a parent provider template.
|
||||
|
||||
A function for obtaining data from the facade resource from within the
|
||||
|
@ -608,6 +791,26 @@ class ResourceFacade(cfn_funcs.ResourceFacade):
|
|||
'metadata', 'deletion_policy', 'update_policy'
|
||||
)
|
||||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(ResourceFacade, self).__init__(stack, fn_name, args)
|
||||
|
||||
if self.args not in self._RESOURCE_ATTRIBUTES:
|
||||
fmt_data = {'fn_name': self.fn_name,
|
||||
'allowed': ', '.join(self._RESOURCE_ATTRIBUTES)}
|
||||
raise ValueError(_('Incorrect arguments to "%(fn_name)s" '
|
||||
'should be one of: %(allowed)s') % fmt_data)
|
||||
|
||||
def result(self):
|
||||
attr = function.resolve(self.args)
|
||||
|
||||
if attr == self.METADATA:
|
||||
return self.stack.parent_resource.metadata_get()
|
||||
elif attr == self.UPDATE_POLICY:
|
||||
up = self.stack.parent_resource.t._update_policy or {}
|
||||
return function.resolve(up)
|
||||
elif attr == self.DELETION_POLICY:
|
||||
return self.stack.parent_resource.t.deletion_policy()
|
||||
|
||||
|
||||
class Removed(function.Function):
|
||||
"""This function existed in previous versions of HOT, but has been removed.
|
||||
|
@ -767,14 +970,11 @@ class StrSplit(function.Function):
|
|||
|
||||
Takes the form::
|
||||
|
||||
str_split: [delimiter, string, <index> ]
|
||||
|
||||
or::
|
||||
|
||||
str_split:
|
||||
- delimiter
|
||||
- string
|
||||
- <delimiter>
|
||||
- <string>
|
||||
- <index>
|
||||
|
||||
If <index> is specified, the specified list item will be returned
|
||||
otherwise, the whole list is returned, similar to get_attr with
|
||||
path based attributes accessing lists.
|
||||
|
@ -901,7 +1101,9 @@ class Equals(function.Function):
|
|||
|
||||
Takes the form::
|
||||
|
||||
{ "equals" : ["value_1", "value_2"] }
|
||||
equals:
|
||||
- <value_1>
|
||||
- <value_2>
|
||||
|
||||
The value can be any type that you want to compare. Returns true
|
||||
if the two values are equal or false if they aren't.
|
||||
|
@ -931,7 +1133,10 @@ class If(function.Macro):
|
|||
|
||||
Takes the form::
|
||||
|
||||
{ "if" : [condition_name, value_if_true, value_if_false] }
|
||||
if:
|
||||
- <condition_name>
|
||||
- <value_if_true>
|
||||
- <value_if_false>
|
||||
|
||||
The value_if_true to be returned if the specified condition evaluates
|
||||
to true, the value_if_false to be returned if the specified condition
|
||||
|
@ -958,11 +1163,11 @@ class If(function.Macro):
|
|||
|
||||
|
||||
class Not(function.Function):
|
||||
"""A function acts as a NOT operator.
|
||||
"""A function that acts as a NOT operator on a condition.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "not" : condition }
|
||||
not: <condition>
|
||||
|
||||
Returns true for a condition that evaluates to false or
|
||||
returns false for a condition that evaluates to true.
|
||||
|
@ -970,10 +1175,13 @@ class Not(function.Function):
|
|||
|
||||
def __init__(self, stack, fn_name, args):
|
||||
super(Not, self).__init__(stack, fn_name, args)
|
||||
self.condition = self._get_condition()
|
||||
|
||||
def _get_condition(self):
|
||||
try:
|
||||
if not self.args:
|
||||
raise ValueError()
|
||||
self.condition = self.args
|
||||
return self.args
|
||||
except ValueError:
|
||||
msg = _('Arguments to "%s" must be of the form: '
|
||||
'condition')
|
||||
|
@ -989,11 +1197,14 @@ class Not(function.Function):
|
|||
|
||||
|
||||
class And(function.Function):
|
||||
"""A function acts as an AND operator.
|
||||
"""A function that acts as an AND operator on conditions.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "and" : [{condition_1}, {condition_2}, {...}, {condition_n}] }
|
||||
and:
|
||||
- <condition_1>
|
||||
- <condition_2>
|
||||
- ...
|
||||
|
||||
Returns true if all the specified conditions evaluate to true, or returns
|
||||
false if any one of the conditions evaluates to false. The minimum number
|
||||
|
@ -1025,11 +1236,14 @@ class And(function.Function):
|
|||
|
||||
|
||||
class Or(function.Function):
|
||||
"""A function acts as an OR operator to evaluate all the conditions.
|
||||
"""A function that acts as an OR operator on conditions.
|
||||
|
||||
Takes the form::
|
||||
|
||||
{ "or" : [{condition_1}, {condition_2}, {...}, {condition_n}] }
|
||||
or:
|
||||
- <condition_1>
|
||||
- <condition_2>
|
||||
- ...
|
||||
|
||||
Returns true if any one of the specified conditions evaluate to true,
|
||||
or returns false if all of the conditions evaluates to false. The minimum
|
||||
|
|
|
@ -73,7 +73,7 @@ class HOTemplate20130523(template_common.CommonTemplate):
|
|||
functions = {
|
||||
'Fn::GetAZs': cfn_funcs.GetAZs,
|
||||
'get_param': hot_funcs.GetParam,
|
||||
'get_resource': cfn_funcs.ResourceRef,
|
||||
'get_resource': hot_funcs.GetResource,
|
||||
'Ref': cfn_funcs.Ref,
|
||||
'get_attr': hot_funcs.GetAttThenSelect,
|
||||
'Fn::Select': cfn_funcs.Select,
|
||||
|
@ -302,7 +302,7 @@ class HOTemplate20141016(HOTemplate20130523):
|
|||
'get_attr': hot_funcs.GetAtt,
|
||||
'get_file': hot_funcs.GetFile,
|
||||
'get_param': hot_funcs.GetParam,
|
||||
'get_resource': cfn_funcs.ResourceRef,
|
||||
'get_resource': hot_funcs.GetResource,
|
||||
'list_join': hot_funcs.Join,
|
||||
'resource_facade': hot_funcs.ResourceFacade,
|
||||
'str_replace': hot_funcs.Replace,
|
||||
|
@ -326,7 +326,7 @@ class HOTemplate20150430(HOTemplate20141016):
|
|||
'get_attr': hot_funcs.GetAtt,
|
||||
'get_file': hot_funcs.GetFile,
|
||||
'get_param': hot_funcs.GetParam,
|
||||
'get_resource': cfn_funcs.ResourceRef,
|
||||
'get_resource': hot_funcs.GetResource,
|
||||
'list_join': hot_funcs.Join,
|
||||
'repeat': hot_funcs.Repeat,
|
||||
'resource_facade': hot_funcs.ResourceFacade,
|
||||
|
@ -354,7 +354,7 @@ class HOTemplate20151015(HOTemplate20150430):
|
|||
'get_attr': hot_funcs.GetAttAllAttributes,
|
||||
'get_file': hot_funcs.GetFile,
|
||||
'get_param': hot_funcs.GetParam,
|
||||
'get_resource': cfn_funcs.ResourceRef,
|
||||
'get_resource': hot_funcs.GetResource,
|
||||
'list_join': hot_funcs.JoinMultiple,
|
||||
'repeat': hot_funcs.Repeat,
|
||||
'resource_facade': hot_funcs.ResourceFacade,
|
||||
|
@ -386,7 +386,7 @@ class HOTemplate20160408(HOTemplate20151015):
|
|||
'get_attr': hot_funcs.GetAttAllAttributes,
|
||||
'get_file': hot_funcs.GetFile,
|
||||
'get_param': hot_funcs.GetParam,
|
||||
'get_resource': cfn_funcs.ResourceRef,
|
||||
'get_resource': hot_funcs.GetResource,
|
||||
'list_join': hot_funcs.JoinMultiple,
|
||||
'repeat': hot_funcs.Repeat,
|
||||
'resource_facade': hot_funcs.ResourceFacade,
|
||||
|
@ -459,7 +459,7 @@ class HOTemplate20161014(HOTemplate20160408):
|
|||
'get_attr': hot_funcs.GetAttAllAttributes,
|
||||
'get_file': hot_funcs.GetFile,
|
||||
'get_param': hot_funcs.GetParam,
|
||||
'get_resource': cfn_funcs.ResourceRef,
|
||||
'get_resource': hot_funcs.GetResource,
|
||||
'list_join': hot_funcs.JoinMultiple,
|
||||
'repeat': hot_funcs.RepeatWithMap,
|
||||
'resource_facade': hot_funcs.ResourceFacade,
|
||||
|
|
|
@ -321,7 +321,7 @@ class TranslationRule(object):
|
|||
def _exec_resolve(self, translation_key, translation_data):
|
||||
|
||||
def resolve_and_find(translation_value):
|
||||
if isinstance(translation_value, cfn_funcs.ResourceRef):
|
||||
if isinstance(translation_value, hot_funcs.GetResource):
|
||||
return
|
||||
if isinstance(translation_value, function.Function):
|
||||
translation_value = function.resolve(translation_value)
|
||||
|
|
|
@ -22,8 +22,8 @@ from neutronclient.v2_0 import client as neutronclient
|
|||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.common import timeutils
|
||||
from heat.engine.cfn import functions as cfn_funcs
|
||||
from heat.engine.clients.os import neutron
|
||||
from heat.engine.hot import functions as hot_funcs
|
||||
from heat.engine import rsrc_defn
|
||||
from heat.engine import scheduler
|
||||
from heat.engine import stack as parser
|
||||
|
@ -498,7 +498,7 @@ class NeutronFloatingIPTest(common.HeatTestCase):
|
|||
t = template_format.parse(neutron_floating_no_assoc_template)
|
||||
stack = utils.parse_stack(t)
|
||||
|
||||
p_result = self.patchobject(cfn_funcs.ResourceRef, 'result')
|
||||
p_result = self.patchobject(hot_funcs.GetResource, 'result')
|
||||
p_result.return_value = 'subnet_uuid'
|
||||
# check dependencies for fip resource
|
||||
required_by = set(stack.dependencies.required_by(
|
||||
|
@ -515,7 +515,7 @@ class NeutronFloatingIPTest(common.HeatTestCase):
|
|||
p_show = self.patchobject(neutronclient.Client, 'show_network')
|
||||
p_show.return_value = {'network': {'subnets': ['subnet_uuid']}}
|
||||
|
||||
p_result = self.patchobject(cfn_funcs.ResourceRef, 'result',
|
||||
p_result = self.patchobject(hot_funcs.GetResource, 'result',
|
||||
autospec=True)
|
||||
|
||||
def return_uuid(self):
|
||||
|
|
|
@ -20,8 +20,8 @@ import six
|
|||
|
||||
from heat.common import exception
|
||||
from heat.common import template_format
|
||||
from heat.engine.cfn import functions as cfn_funcs
|
||||
from heat.engine.clients.os import neutron
|
||||
from heat.engine.hot import functions as hot_funcs
|
||||
from heat.engine.resources.openstack.neutron import subnet
|
||||
from heat.engine import rsrc_defn
|
||||
from heat.engine import scheduler
|
||||
|
@ -710,6 +710,6 @@ class NeutronSubnetTest(common.HeatTestCase):
|
|||
rsrc = stack['subnet']
|
||||
stack.create()
|
||||
|
||||
self.assertEqual(cfn_funcs.ResourceRef(stack, 'get_resource', 'net'),
|
||||
self.assertEqual(hot_funcs.GetResource(stack, 'get_resource', 'net'),
|
||||
rsrc.properties.get('network'))
|
||||
self.assertIsNone(rsrc.properties.get('network_id'))
|
||||
|
|
|
@ -15,8 +15,8 @@ from oslo_serialization import jsonutils
|
|||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.engine.cfn import functions as cfn_funcs
|
||||
from heat.engine import constraints
|
||||
from heat.engine.hot import functions as hot_funcs
|
||||
from heat.engine.hot import parameters as hot_param
|
||||
from heat.engine import parameters
|
||||
from heat.engine import plugin_manager
|
||||
|
@ -1235,7 +1235,7 @@ class PropertiesTest(common.HeatTestCase):
|
|||
# define properties with function and constraint
|
||||
props = properties.Properties(
|
||||
schema,
|
||||
{'foo': cfn_funcs.ResourceRef(
|
||||
{'foo': hot_funcs.GetResource(
|
||||
stack, 'get_resource', 'another_res')},
|
||||
test_resolver)
|
||||
|
||||
|
|
|
@ -530,7 +530,7 @@ class TestTranslationRule(common.HeatTestCase):
|
|||
pass
|
||||
|
||||
stack = DummyStack(another_res=rsrc())
|
||||
ref = cfn_funcs.ResourceRef(stack, 'get_resource',
|
||||
ref = hot_funcs.GetResource(stack, 'get_resource',
|
||||
'another_res')
|
||||
data = {
|
||||
'far': [{'red': ref}],
|
||||
|
@ -608,7 +608,7 @@ class TestTranslationRule(common.HeatTestCase):
|
|||
pass
|
||||
|
||||
stack = DummyStack(another_res=rsrc())
|
||||
ref = cfn_funcs.ResourceRef(stack, 'get_resource',
|
||||
ref = hot_funcs.GetResource(stack, 'get_resource',
|
||||
'another_res')
|
||||
data = {'far': ref}
|
||||
props = properties.Properties(schema, data)
|
||||
|
|
Loading…
Reference in New Issue