Make cfn functions inherit from HOT
HOT is our canonical native format, so define the cfn intrinsic functions in terms of the HOT functions instead of the other way around. Ensure each function has a consistently-formatted docstring appropriate to its template type. Change-Id: I743446bdef3fd7472537db75bf9e0fbfb164e36a
This commit is contained in:
parent
0327c339f5
commit
8262265292
|
@ -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,
|
||||
|
|
|
@ -276,7 +276,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