Move built-in functions to separate modules
partial-blueprint function-plugins Change-Id: I6ee08f962935c07d292ab4f3f298fbb5e5176e8f
This commit is contained in:
parent
7ab425ca79
commit
a23fe66753
0
heat/engine/cfn/__init__.py
Normal file
0
heat/engine/cfn/__init__.py
Normal file
567
heat/engine/cfn/functions.py
Normal file
567
heat/engine/cfn/functions.py
Normal file
@ -0,0 +1,567 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
#
|
||||||
|
# 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 collections
|
||||||
|
import json
|
||||||
|
|
||||||
|
from heat.api.aws import utils as aws_utils
|
||||||
|
from heat.common import exception
|
||||||
|
|
||||||
|
from heat.engine import function
|
||||||
|
|
||||||
|
|
||||||
|
class FindInMap(function.Function):
|
||||||
|
'''
|
||||||
|
A function for resolving keys in the template mappings.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Fn::FindInMap" : [ "mapping",
|
||||||
|
"key",
|
||||||
|
"value" ] }
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, stack, fn_name, args):
|
||||||
|
super(FindInMap, self).__init__(stack, fn_name, args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._mapname, self._mapkey, self._mapvalue = self.args
|
||||||
|
except ValueError as ex:
|
||||||
|
raise KeyError(str(ex))
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
mapping = self.stack.t.maps[function.resolve(self._mapname)]
|
||||||
|
key = function.resolve(self._mapkey)
|
||||||
|
value = function.resolve(self._mapvalue)
|
||||||
|
return mapping[key][value]
|
||||||
|
|
||||||
|
|
||||||
|
class GetAZs(function.Function):
|
||||||
|
'''
|
||||||
|
A function for retrieving the availability zones.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Fn::GetAZs" : "<region>" }
|
||||||
|
'''
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
# TODO(therve): Implement region scoping
|
||||||
|
#region = function.resolve(self.args)
|
||||||
|
|
||||||
|
if self.stack is None:
|
||||||
|
return ['nova']
|
||||||
|
else:
|
||||||
|
return self.stack.get_availability_zones()
|
||||||
|
|
||||||
|
|
||||||
|
class ParamRef(function.Function):
|
||||||
|
'''
|
||||||
|
A function for resolving parameter references.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Ref" : "<param_name>" }
|
||||||
|
'''
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
param_name = function.resolve(self.args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.stack.parameters[param_name]
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
raise exception.UserParameterMissing(key=param_name)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceRef(function.Function):
|
||||||
|
'''
|
||||||
|
A function for resolving resource references.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Ref" : "<resource_name>" }
|
||||||
|
'''
|
||||||
|
|
||||||
|
def _resource(self):
|
||||||
|
resource_name = function.resolve(self.args)
|
||||||
|
|
||||||
|
return self.stack[resource_name]
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
return self._resource().FnGetRefId()
|
||||||
|
|
||||||
|
|
||||||
|
def Ref(stack, fn_name, args):
|
||||||
|
'''
|
||||||
|
A function for resolving parameters or resource references.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Ref" : "<param_name>" }
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
{ "Ref" : "<resource_name>" }
|
||||||
|
'''
|
||||||
|
if args in stack.t[stack.t.RESOURCES]:
|
||||||
|
RefClass = ResourceRef
|
||||||
|
else:
|
||||||
|
RefClass = ParamRef
|
||||||
|
return RefClass(stack, fn_name, args)
|
||||||
|
|
||||||
|
|
||||||
|
class GetAtt(function.Function):
|
||||||
|
'''
|
||||||
|
A function for resolving resource attributes.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Fn::GetAtt" : [ "<resource_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
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(_('Arguments to "%s" must be of the form '
|
||||||
|
'[resource_name, attribute]') % self.fn_name)
|
||||||
|
|
||||||
|
return resource_name, attribute
|
||||||
|
|
||||||
|
def _resource(self):
|
||||||
|
resource_name = function.resolve(self._resource_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.stack[resource_name]
|
||||||
|
except KeyError:
|
||||||
|
raise exception.InvalidTemplateAttribute(
|
||||||
|
resource=resource_name,
|
||||||
|
key=function.resolve(self._attribute))
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
attribute = function.resolve(self._attribute)
|
||||||
|
|
||||||
|
r = self._resource()
|
||||||
|
if (r.status in (r.IN_PROGRESS, r.COMPLETE) and
|
||||||
|
r.action in (r.CREATE, r.RESUME, r.UPDATE)):
|
||||||
|
return r.FnGetAtt(attribute)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class Select(function.Function):
|
||||||
|
'''
|
||||||
|
A function for selecting an item from a list or map.
|
||||||
|
|
||||||
|
Takes the form (for a list lookup)::
|
||||||
|
|
||||||
|
{ "Fn::Select" : [ "<index>", [ "<value_1>", "<value_2>", ... ] ] }
|
||||||
|
|
||||||
|
Takes the form (for a map lookup)::
|
||||||
|
|
||||||
|
{ "Fn::Select" : [ "<index>", { "<key_1>": "<value_1>", ... } ] }
|
||||||
|
|
||||||
|
If the selected index is not found, this function resolves to an empty
|
||||||
|
string.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, stack, fn_name, args):
|
||||||
|
super(Select, self).__init__(stack, fn_name, args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._lookup, self._strings = self.args
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(_('Arguments to "%s" must be of the form '
|
||||||
|
'[index, collection]') % self.fn_name)
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
index = function.resolve(self._lookup)
|
||||||
|
|
||||||
|
try:
|
||||||
|
index = int(index)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
strings = function.resolve(self._strings)
|
||||||
|
|
||||||
|
if strings == '':
|
||||||
|
# an empty string is a common response from other
|
||||||
|
# functions when result is not currently available.
|
||||||
|
# Handle by returning an empty string
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if isinstance(strings, basestring):
|
||||||
|
# might be serialized json.
|
||||||
|
try:
|
||||||
|
strings = json.loads(strings)
|
||||||
|
except ValueError as json_ex:
|
||||||
|
fmt_data = {'fn_name': self.fn_name,
|
||||||
|
'err': json_ex}
|
||||||
|
raise ValueError(_('"%(fn_name)s": %(err)s') % fmt_data)
|
||||||
|
|
||||||
|
if isinstance(strings, collections.Mapping):
|
||||||
|
if not isinstance(index, basestring):
|
||||||
|
raise TypeError(_('Index to "%s" must be a string') %
|
||||||
|
self.fn_name)
|
||||||
|
return strings.get(index, '')
|
||||||
|
|
||||||
|
if (isinstance(strings, collections.Sequence) and
|
||||||
|
not isinstance(strings, basestring)):
|
||||||
|
if not isinstance(index, (int, long)):
|
||||||
|
raise TypeError(_('Index to "%s" must be an integer') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return strings[index]
|
||||||
|
except IndexError:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if strings is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
raise TypeError(_('Arguments to %s not fully resolved') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
|
||||||
|
class Join(function.Function):
|
||||||
|
'''
|
||||||
|
A function for joining strings.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Fn::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 isinstance(self.args, (basestring, collections.Mapping)):
|
||||||
|
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 (isinstance(strings, basestring) 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, basestring):
|
||||||
|
raise TypeError(_('"%s" delimiter must be a string') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
def ensure_string(s):
|
||||||
|
if s is None:
|
||||||
|
return ''
|
||||||
|
if not isinstance(s, basestring):
|
||||||
|
raise TypeError(_('Items to join must be strings'))
|
||||||
|
return s
|
||||||
|
|
||||||
|
return delim.join(ensure_string(s) for s in strings)
|
||||||
|
|
||||||
|
|
||||||
|
class Split(function.Function):
|
||||||
|
'''
|
||||||
|
A function for splitting strings.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Fn::Split" : [ "<delim>", "<string_1><delim><string_2>..." ] }
|
||||||
|
|
||||||
|
And resolves to::
|
||||||
|
|
||||||
|
[ "<string_1>", "<string_2>", ... ]
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, stack, fn_name, args):
|
||||||
|
super(Split, self).__init__(stack, fn_name, args)
|
||||||
|
|
||||||
|
example = '"%s" : [ ",", "str1,str2"]]' % self.fn_name
|
||||||
|
fmt_data = {'fn_name': self.fn_name,
|
||||||
|
'example': example}
|
||||||
|
|
||||||
|
if isinstance(self.args, (basestring, collections.Mapping)):
|
||||||
|
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 not isinstance(self._delim, basestring):
|
||||||
|
raise TypeError(_("Delimiter for %s must be string") %
|
||||||
|
self.fn_name)
|
||||||
|
if not isinstance(strings, basestring):
|
||||||
|
raise TypeError(_("String to split must be string; got %s") %
|
||||||
|
type(strings))
|
||||||
|
|
||||||
|
return strings.split(self._delim)
|
||||||
|
|
||||||
|
|
||||||
|
class Replace(function.Function):
|
||||||
|
'''
|
||||||
|
A function for performing string subsitutions.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Fn::Replace" : [
|
||||||
|
{ "<key_1>": "<value_1>", "<key_2>": "<value_2>", ... },
|
||||||
|
"<key_1> <key_2>"
|
||||||
|
] }
|
||||||
|
|
||||||
|
And resolves to::
|
||||||
|
|
||||||
|
"<value_1> <value_2>"
|
||||||
|
|
||||||
|
This is implemented using python str.replace on each key. The order in
|
||||||
|
which replacements are performed is 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):
|
||||||
|
raise TypeError(_('"%s" parameters must be a mapping') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
def _parse_args(self):
|
||||||
|
|
||||||
|
example = ('{"%s": '
|
||||||
|
'[ {"$var1": "foo", "%%var2%%": "bar"}, '
|
||||||
|
'"$var1 is %%var2%%"]}' % self.fn_name)
|
||||||
|
fmt_data = {'fn_name': self.fn_name,
|
||||||
|
'example': example}
|
||||||
|
|
||||||
|
if isinstance(self.args, (basestring, collections.Mapping)):
|
||||||
|
raise TypeError(_('Incorrect arguments to "%(fn_name)s" '
|
||||||
|
'should be: %(example)s') % fmt_data)
|
||||||
|
|
||||||
|
try:
|
||||||
|
mapping, string = self.args
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(_('Incorrect arguments to "%(fn_name)s" '
|
||||||
|
'should be: %(example)s') % fmt_data)
|
||||||
|
else:
|
||||||
|
return mapping, string
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
template = function.resolve(self._string)
|
||||||
|
mapping = function.resolve(self._mapping)
|
||||||
|
|
||||||
|
if not isinstance(template, basestring):
|
||||||
|
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, basestring):
|
||||||
|
raise TypeError(_('"%s" param placeholders must be strings') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
value = ''
|
||||||
|
|
||||||
|
if not isinstance(value, (basestring, int, long, float, bool)):
|
||||||
|
raise TypeError(_('"%s" params must be strings or numbers') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
return string.replace(placeholder, unicode(value))
|
||||||
|
|
||||||
|
return reduce(replace, mapping.iteritems(), template)
|
||||||
|
|
||||||
|
|
||||||
|
class Base64(function.Function):
|
||||||
|
'''
|
||||||
|
A placeholder function for converting to base64.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Fn::Base64" : "<string>" }
|
||||||
|
|
||||||
|
This function actually performs no conversion. It is included for the
|
||||||
|
benefit of templates that convert UserData to Base64. Heat accepts UserData
|
||||||
|
in plain text.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
resolved = function.resolve(self.args)
|
||||||
|
if not isinstance(resolved, basestring):
|
||||||
|
raise TypeError(_('"%s" argument must be a string') % self.fn_name)
|
||||||
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
|
class MemberListToMap(function.Function):
|
||||||
|
'''
|
||||||
|
A function for converting lists containing enumerated keys and values to
|
||||||
|
a mapping.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ 'Fn::MemberListToMap' : [ 'Name',
|
||||||
|
'Value',
|
||||||
|
[ '.member.0.Name=<key_0>',
|
||||||
|
'.member.0.Value=<value_0>',
|
||||||
|
... ] ] }
|
||||||
|
|
||||||
|
And resolves to::
|
||||||
|
|
||||||
|
{ "<key_0>" : "<value_0>", ... }
|
||||||
|
|
||||||
|
The first two arguments are the names of the key and value.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, stack, fn_name, args):
|
||||||
|
super(MemberListToMap, self).__init__(stack, fn_name, args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._keyname, self._valuename, self._list = self.args
|
||||||
|
except ValueError:
|
||||||
|
correct = '''
|
||||||
|
{'Fn::MemberListToMap': ['Name', 'Value',
|
||||||
|
['.member.0.Name=key',
|
||||||
|
'.member.0.Value=door']]}
|
||||||
|
'''
|
||||||
|
raise TypeError(_('Wrong Arguments try: "%s"') % correct)
|
||||||
|
|
||||||
|
if not isinstance(self._keyname, basestring):
|
||||||
|
raise TypeError(_('%s Key Name must be a string') % self.fn_name)
|
||||||
|
|
||||||
|
if not isinstance(self._valuename, basestring):
|
||||||
|
raise TypeError(_('%s Value Name must be a string') % self.fn_name)
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
member_list = function.resolve(self._list)
|
||||||
|
|
||||||
|
if not isinstance(member_list, collections.Iterable):
|
||||||
|
raise TypeError(_('Member list must be a list'))
|
||||||
|
|
||||||
|
def item(s):
|
||||||
|
if not isinstance(s, basestring):
|
||||||
|
raise TypeError(_("Member list items must be strings"))
|
||||||
|
return s.split('=', 1)
|
||||||
|
|
||||||
|
partials = dict(item(s) for s in member_list)
|
||||||
|
return aws_utils.extract_param_pairs(partials,
|
||||||
|
prefix='',
|
||||||
|
keyname=self._keyname,
|
||||||
|
valuename=self._valuename)
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceFacade(function.Function):
|
||||||
|
'''
|
||||||
|
A function for obtaining data from the facade resource from within the
|
||||||
|
corresponding provider template.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
{ "Fn::ResourceFacade": "<attribute_type>" }
|
||||||
|
|
||||||
|
where the valid attribute types are "Metadata", "DeletionPolicy" and
|
||||||
|
"UpdatePolicy".
|
||||||
|
'''
|
||||||
|
|
||||||
|
_RESOURCE_ATTRIBUTES = (
|
||||||
|
METADATA, DELETION_POLICY, UPDATE_POLICY,
|
||||||
|
) = (
|
||||||
|
'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
|
||||||
|
elif attr == self.UPDATE_POLICY:
|
||||||
|
return self.stack.parent_resource.t.get(attr, {})
|
||||||
|
elif attr == self.DELETION_POLICY:
|
||||||
|
try:
|
||||||
|
return self.stack.parent_resource.t[attr]
|
||||||
|
except KeyError:
|
||||||
|
# TODO(zaneb): This should have a default!
|
||||||
|
fmt_data = {'fn_name': self.fn_name,
|
||||||
|
'key': attr}
|
||||||
|
raise KeyError(_('"%(fn_name)s" '
|
||||||
|
'key "%(key)s" not found') % fmt_data)
|
||||||
|
|
||||||
|
|
||||||
|
def function_mapping(version_key, version):
|
||||||
|
if version_key == 'AWSTemplateFormatVersion':
|
||||||
|
return {
|
||||||
|
'Fn::FindInMap': FindInMap,
|
||||||
|
'Fn::GetAZs': GetAZs,
|
||||||
|
'Ref': Ref,
|
||||||
|
'Fn::GetAtt': GetAtt,
|
||||||
|
'Fn::Select': Select,
|
||||||
|
'Fn::Join': Join,
|
||||||
|
'Fn::Base64': Base64,
|
||||||
|
}
|
||||||
|
elif version_key != 'HeatTemplateFormatVersion':
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if version == '2012-12-12':
|
||||||
|
return {
|
||||||
|
'Fn::FindInMap': FindInMap,
|
||||||
|
'Fn::GetAZs': GetAZs,
|
||||||
|
'Ref': Ref,
|
||||||
|
'Fn::GetAtt': GetAtt,
|
||||||
|
'Fn::Select': Select,
|
||||||
|
'Fn::Join': Join,
|
||||||
|
'Fn::Split': Split,
|
||||||
|
'Fn::Replace': Replace,
|
||||||
|
'Fn::Base64': Base64,
|
||||||
|
'Fn::MemberListToMap': MemberListToMap,
|
||||||
|
'Fn::ResourceFacade': ResourceFacade,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
@ -12,10 +12,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import collections
|
|
||||||
|
|
||||||
from heat.common import exception
|
from heat.common import exception
|
||||||
from heat.engine import function
|
|
||||||
from heat.engine import template
|
from heat.engine import template
|
||||||
from heat.engine import parameters
|
from heat.engine import parameters
|
||||||
from heat.engine import constraints as constr
|
from heat.engine import constraints as constr
|
||||||
@ -154,202 +151,8 @@ class HOTemplate(template.Template):
|
|||||||
validate_value=validate_value, context=context)
|
validate_value=validate_value, context=context)
|
||||||
|
|
||||||
def functions(self):
|
def functions(self):
|
||||||
return {
|
from heat.engine.hot import functions
|
||||||
'Fn::FindInMap': template.FindInMap,
|
return functions.function_mapping(*self.version())
|
||||||
'Fn::GetAZs': template.GetAZs,
|
|
||||||
'get_param': GetParam,
|
|
||||||
'get_resource': template.ResourceRef,
|
|
||||||
'Ref': template.Ref,
|
|
||||||
'get_attr': GetAtt,
|
|
||||||
'Fn::Select': template.Select,
|
|
||||||
'Fn::Join': template.Join,
|
|
||||||
'Fn::Split': template.Split,
|
|
||||||
'str_replace': Replace,
|
|
||||||
'Fn::Replace': template.Replace,
|
|
||||||
'Fn::Base64': template.Base64,
|
|
||||||
'Fn::MemberListToMap': template.MemberListToMap,
|
|
||||||
'Fn::ResourceFacade': template.ResourceFacade,
|
|
||||||
'get_file': GetFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GetParam(function.Function):
|
|
||||||
'''
|
|
||||||
A function for resolving parameter references.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
get_param: <param_name>
|
|
||||||
|
|
||||||
or::
|
|
||||||
|
|
||||||
get_param:
|
|
||||||
- <param_name>
|
|
||||||
- <path1>
|
|
||||||
- ...
|
|
||||||
'''
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
args = function.resolve(self.args)
|
|
||||||
|
|
||||||
if not args:
|
|
||||||
raise ValueError(_('Function "%s" must have arguments') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
if isinstance(args, basestring):
|
|
||||||
param_name = args
|
|
||||||
path_components = []
|
|
||||||
elif isinstance(args, collections.Sequence):
|
|
||||||
param_name = args[0]
|
|
||||||
path_components = args[1:]
|
|
||||||
else:
|
|
||||||
raise TypeError(_('Argument to "%s" must be string or list') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
if not isinstance(param_name, basestring):
|
|
||||||
raise TypeError(_('Parameter name in "%s" must be string') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
parameter = self.stack.parameters[param_name]
|
|
||||||
except KeyError:
|
|
||||||
raise exception.UserParameterMissing(key=param_name)
|
|
||||||
|
|
||||||
def get_path_component(collection, key):
|
|
||||||
if not isinstance(collection, (collections.Mapping,
|
|
||||||
collections.Sequence)):
|
|
||||||
raise TypeError(_('"%s" can\'t traverse path') % self.fn_name)
|
|
||||||
|
|
||||||
if not isinstance(key, (basestring, int)):
|
|
||||||
raise TypeError(_('Path components in "%s" '
|
|
||||||
'must be strings') % self.fn_name)
|
|
||||||
|
|
||||||
return collection[key]
|
|
||||||
|
|
||||||
try:
|
|
||||||
return reduce(get_path_component, path_components, parameter)
|
|
||||||
except (KeyError, IndexError, TypeError):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
class GetAtt(template.GetAtt):
|
|
||||||
'''
|
|
||||||
A function for resolving resource attributes.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
get_attr:
|
|
||||||
- <resource_name>
|
|
||||||
- <attribute_name>
|
|
||||||
- <path1>
|
|
||||||
- ...
|
|
||||||
'''
|
|
||||||
|
|
||||||
def _parse_args(self):
|
|
||||||
if (not isinstance(self.args, collections.Sequence) or
|
|
||||||
isinstance(self.args, basestring)):
|
|
||||||
raise TypeError(_('Argument to "%s" must be a list') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
if len(self.args) < 2:
|
|
||||||
raise ValueError(_('Arguments to "%s" must be of the form '
|
|
||||||
'[resource_name, attribute, (path), ...]') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
self._path_components = self.args[2:]
|
|
||||||
|
|
||||||
return tuple(self.args[:2])
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
attribute = super(GetAtt, self).result()
|
|
||||||
if attribute is None:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
path_components = function.resolve(self._path_components)
|
|
||||||
|
|
||||||
def get_path_component(collection, key):
|
|
||||||
if not isinstance(collection, (collections.Mapping,
|
|
||||||
collections.Sequence)):
|
|
||||||
raise TypeError(_('"%s" can\'t traverse path') % self.fn_name)
|
|
||||||
|
|
||||||
if not isinstance(key, (basestring, int)):
|
|
||||||
raise TypeError(_('Path components in "%s" '
|
|
||||||
'must be strings') % self.fn_name)
|
|
||||||
|
|
||||||
return collection[key]
|
|
||||||
|
|
||||||
try:
|
|
||||||
return reduce(get_path_component, path_components, attribute)
|
|
||||||
except (KeyError, IndexError, TypeError):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
class Replace(template.Replace):
|
|
||||||
'''
|
|
||||||
A function for performing string substitutions.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
str_replace:
|
|
||||||
template: <key_1> <key_2>
|
|
||||||
params:
|
|
||||||
<key_1>: <value_1>
|
|
||||||
<key_2>: <value_2>
|
|
||||||
...
|
|
||||||
|
|
||||||
And resolves to::
|
|
||||||
|
|
||||||
"<value_1> <value_2>"
|
|
||||||
|
|
||||||
This is implemented using Python's str.replace on each key. The order in
|
|
||||||
which replacements are performed is undefined.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def _parse_args(self):
|
|
||||||
if not isinstance(self.args, collections.Mapping):
|
|
||||||
raise TypeError(_('Arguments to "%s" must be a map') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
mapping = self.args['params']
|
|
||||||
string = self.args['template']
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
example = ('''str_replace:
|
|
||||||
template: This is var1 template var2
|
|
||||||
params:
|
|
||||||
var1: a
|
|
||||||
var2: string''')
|
|
||||||
raise KeyError(_('"str_replace" syntax should be %s') %
|
|
||||||
example)
|
|
||||||
else:
|
|
||||||
return mapping, string
|
|
||||||
|
|
||||||
|
|
||||||
class GetFile(function.Function):
|
|
||||||
"""
|
|
||||||
A function for including a file inline.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
get_file: <file_key>
|
|
||||||
|
|
||||||
And resolves to the content stored in the files dictionary under the given
|
|
||||||
key.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
args = function.resolve(self.args)
|
|
||||||
if not (isinstance(args, basestring)):
|
|
||||||
raise TypeError(_('Argument to "%s" must be a string') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
f = self.stack.t.files.get(args)
|
|
||||||
if f is None:
|
|
||||||
fmt_data = {'fn_name': self.fn_name,
|
|
||||||
'file_key': args}
|
|
||||||
raise ValueError(_('No content found in the "files" section for '
|
|
||||||
'%(fn_name)s path: %(file_key)s') % fmt_data)
|
|
||||||
return f
|
|
||||||
|
|
||||||
|
|
||||||
class HOTParamSchema(parameters.Schema):
|
class HOTParamSchema(parameters.Schema):
|
||||||
|
225
heat/engine/hot/functions.py
Normal file
225
heat/engine/hot/functions.py
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
#
|
||||||
|
# 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 collections
|
||||||
|
|
||||||
|
from heat.common import exception
|
||||||
|
|
||||||
|
from heat.engine.cfn import functions as cfn_funcs
|
||||||
|
from heat.engine import function
|
||||||
|
|
||||||
|
|
||||||
|
class GetParam(function.Function):
|
||||||
|
'''
|
||||||
|
A function for resolving parameter references.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
get_param: <param_name>
|
||||||
|
|
||||||
|
or::
|
||||||
|
|
||||||
|
get_param:
|
||||||
|
- <param_name>
|
||||||
|
- <path1>
|
||||||
|
- ...
|
||||||
|
'''
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
args = function.resolve(self.args)
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
raise ValueError(_('Function "%s" must have arguments') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
if isinstance(args, basestring):
|
||||||
|
param_name = args
|
||||||
|
path_components = []
|
||||||
|
elif isinstance(args, collections.Sequence):
|
||||||
|
param_name = args[0]
|
||||||
|
path_components = args[1:]
|
||||||
|
else:
|
||||||
|
raise TypeError(_('Argument to "%s" must be string or list') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
if not isinstance(param_name, basestring):
|
||||||
|
raise TypeError(_('Parameter name in "%s" must be string') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
parameter = self.stack.parameters[param_name]
|
||||||
|
except KeyError:
|
||||||
|
raise exception.UserParameterMissing(key=param_name)
|
||||||
|
|
||||||
|
def get_path_component(collection, key):
|
||||||
|
if not isinstance(collection, (collections.Mapping,
|
||||||
|
collections.Sequence)):
|
||||||
|
raise TypeError(_('"%s" can\'t traverse path') % self.fn_name)
|
||||||
|
|
||||||
|
if not isinstance(key, (basestring, int)):
|
||||||
|
raise TypeError(_('Path components in "%s" '
|
||||||
|
'must be strings') % self.fn_name)
|
||||||
|
|
||||||
|
return collection[key]
|
||||||
|
|
||||||
|
try:
|
||||||
|
return reduce(get_path_component, path_components, parameter)
|
||||||
|
except (KeyError, IndexError, TypeError):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
class GetAtt(cfn_funcs.GetAtt):
|
||||||
|
'''
|
||||||
|
A function for resolving resource attributes.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
get_attr:
|
||||||
|
- <resource_name>
|
||||||
|
- <attribute_name>
|
||||||
|
- <path1>
|
||||||
|
- ...
|
||||||
|
'''
|
||||||
|
|
||||||
|
def _parse_args(self):
|
||||||
|
if (not isinstance(self.args, collections.Sequence) or
|
||||||
|
isinstance(self.args, basestring)):
|
||||||
|
raise TypeError(_('Argument to "%s" must be a list') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
if len(self.args) < 2:
|
||||||
|
raise ValueError(_('Arguments to "%s" must be of the form '
|
||||||
|
'[resource_name, attribute, (path), ...]') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
self._path_components = self.args[2:]
|
||||||
|
|
||||||
|
return tuple(self.args[:2])
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
attribute = super(GetAtt, self).result()
|
||||||
|
if attribute is None:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
path_components = function.resolve(self._path_components)
|
||||||
|
|
||||||
|
def get_path_component(collection, key):
|
||||||
|
if not isinstance(collection, (collections.Mapping,
|
||||||
|
collections.Sequence)):
|
||||||
|
raise TypeError(_('"%s" can\'t traverse path') % self.fn_name)
|
||||||
|
|
||||||
|
if not isinstance(key, (basestring, int)):
|
||||||
|
raise TypeError(_('Path components in "%s" '
|
||||||
|
'must be strings') % self.fn_name)
|
||||||
|
|
||||||
|
return collection[key]
|
||||||
|
|
||||||
|
try:
|
||||||
|
return reduce(get_path_component, path_components, attribute)
|
||||||
|
except (KeyError, IndexError, TypeError):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
class Replace(cfn_funcs.Replace):
|
||||||
|
'''
|
||||||
|
A function for performing string substitutions.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
str_replace:
|
||||||
|
template: <key_1> <key_2>
|
||||||
|
params:
|
||||||
|
<key_1>: <value_1>
|
||||||
|
<key_2>: <value_2>
|
||||||
|
...
|
||||||
|
|
||||||
|
And resolves to::
|
||||||
|
|
||||||
|
"<value_1> <value_2>"
|
||||||
|
|
||||||
|
This is implemented using Python's str.replace on each key. The order in
|
||||||
|
which replacements are performed is undefined.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def _parse_args(self):
|
||||||
|
if not isinstance(self.args, collections.Mapping):
|
||||||
|
raise TypeError(_('Arguments to "%s" must be a map') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
mapping = self.args['params']
|
||||||
|
string = self.args['template']
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
example = ('''str_replace:
|
||||||
|
template: This is var1 template var2
|
||||||
|
params:
|
||||||
|
var1: a
|
||||||
|
var2: string''')
|
||||||
|
raise KeyError(_('"str_replace" syntax should be %s') %
|
||||||
|
example)
|
||||||
|
else:
|
||||||
|
return mapping, string
|
||||||
|
|
||||||
|
|
||||||
|
class GetFile(function.Function):
|
||||||
|
"""
|
||||||
|
A function for including a file inline.
|
||||||
|
|
||||||
|
Takes the form::
|
||||||
|
|
||||||
|
get_file: <file_key>
|
||||||
|
|
||||||
|
And resolves to the content stored in the files dictionary under the given
|
||||||
|
key.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
args = function.resolve(self.args)
|
||||||
|
if not (isinstance(args, basestring)):
|
||||||
|
raise TypeError(_('Argument to "%s" must be a string') %
|
||||||
|
self.fn_name)
|
||||||
|
|
||||||
|
f = self.stack.t.files.get(args)
|
||||||
|
if f is None:
|
||||||
|
fmt_data = {'fn_name': self.fn_name,
|
||||||
|
'file_key': args}
|
||||||
|
raise ValueError(_('No content found in the "files" section for '
|
||||||
|
'%(fn_name)s path: %(file_key)s') % fmt_data)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def function_mapping(version_key, version):
|
||||||
|
if version_key != 'heat_template_version':
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if version == '2013-05-23':
|
||||||
|
return {
|
||||||
|
'Fn::FindInMap': cfn_funcs.FindInMap,
|
||||||
|
'Fn::GetAZs': cfn_funcs.GetAZs,
|
||||||
|
'get_param': GetParam,
|
||||||
|
'get_resource': cfn_funcs.ResourceRef,
|
||||||
|
'Ref': cfn_funcs.Ref,
|
||||||
|
'get_attr': GetAtt,
|
||||||
|
'Fn::Select': cfn_funcs.Select,
|
||||||
|
'Fn::Join': cfn_funcs.Join,
|
||||||
|
'Fn::Split': cfn_funcs.Split,
|
||||||
|
'str_replace': Replace,
|
||||||
|
'Fn::Replace': cfn_funcs.Replace,
|
||||||
|
'Fn::Base64': cfn_funcs.Base64,
|
||||||
|
'Fn::MemberListToMap': cfn_funcs.MemberListToMap,
|
||||||
|
'Fn::ResourceFacade': cfn_funcs.ResourceFacade,
|
||||||
|
'get_file': GetFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}
|
@ -15,13 +15,10 @@
|
|||||||
|
|
||||||
import collections
|
import collections
|
||||||
import functools
|
import functools
|
||||||
import json
|
|
||||||
|
|
||||||
from heat.api.aws import utils as aws_utils
|
|
||||||
from heat.db import api as db_api
|
from heat.db import api as db_api
|
||||||
from heat.common import exception
|
|
||||||
from heat.engine import parameters
|
from heat.engine import parameters
|
||||||
from heat.engine import function
|
from heat.engine.cfn import functions
|
||||||
|
|
||||||
|
|
||||||
class Template(collections.Mapping):
|
class Template(collections.Mapping):
|
||||||
@ -118,32 +115,7 @@ class Template(collections.Mapping):
|
|||||||
context=context)
|
context=context)
|
||||||
|
|
||||||
def functions(self):
|
def functions(self):
|
||||||
version_key, version = self.version()
|
return functions.function_mapping(*self.version())
|
||||||
|
|
||||||
if version_key == self.VERSION:
|
|
||||||
return {
|
|
||||||
'Fn::FindInMap': FindInMap,
|
|
||||||
'Fn::GetAZs': GetAZs,
|
|
||||||
'Ref': Ref,
|
|
||||||
'Fn::GetAtt': GetAtt,
|
|
||||||
'Fn::Select': Select,
|
|
||||||
'Fn::Join': Join,
|
|
||||||
'Fn::Base64': Base64,
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'Fn::FindInMap': FindInMap,
|
|
||||||
'Fn::GetAZs': GetAZs,
|
|
||||||
'Ref': Ref,
|
|
||||||
'Fn::GetAtt': GetAtt,
|
|
||||||
'Fn::Select': Select,
|
|
||||||
'Fn::Join': Join,
|
|
||||||
'Fn::Split': Split,
|
|
||||||
'Fn::Replace': Replace,
|
|
||||||
'Fn::Base64': Base64,
|
|
||||||
'Fn::MemberListToMap': MemberListToMap,
|
|
||||||
'Fn::ResourceFacade': ResourceFacade,
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(self, stack, snippet):
|
def parse(self, stack, snippet):
|
||||||
parse = functools.partial(self.parse, stack)
|
parse = functools.partial(self.parse, stack)
|
||||||
@ -160,516 +132,3 @@ class Template(collections.Mapping):
|
|||||||
return [parse(v) for v in snippet]
|
return [parse(v) for v in snippet]
|
||||||
else:
|
else:
|
||||||
return snippet
|
return snippet
|
||||||
|
|
||||||
|
|
||||||
class FindInMap(function.Function):
|
|
||||||
'''
|
|
||||||
A function for resolving keys in the template mappings.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Fn::FindInMap" : [ "mapping",
|
|
||||||
"key",
|
|
||||||
"value" ] }
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, stack, fn_name, args):
|
|
||||||
super(FindInMap, self).__init__(stack, fn_name, args)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._mapname, self._mapkey, self._mapvalue = self.args
|
|
||||||
except ValueError as ex:
|
|
||||||
raise KeyError(str(ex))
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
mapping = self.stack.t.maps[function.resolve(self._mapname)]
|
|
||||||
key = function.resolve(self._mapkey)
|
|
||||||
value = function.resolve(self._mapvalue)
|
|
||||||
return mapping[key][value]
|
|
||||||
|
|
||||||
|
|
||||||
class GetAZs(function.Function):
|
|
||||||
'''
|
|
||||||
A function for retrieving the availability zones.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Fn::GetAZs" : "<region>" }
|
|
||||||
'''
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
# TODO(therve): Implement region scoping
|
|
||||||
#region = function.resolve(self.args)
|
|
||||||
|
|
||||||
if self.stack is None:
|
|
||||||
return ['nova']
|
|
||||||
else:
|
|
||||||
return self.stack.get_availability_zones()
|
|
||||||
|
|
||||||
|
|
||||||
class ParamRef(function.Function):
|
|
||||||
'''
|
|
||||||
A function for resolving parameter references.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Ref" : "<param_name>" }
|
|
||||||
'''
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
param_name = function.resolve(self.args)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return self.stack.parameters[param_name]
|
|
||||||
except (KeyError, ValueError):
|
|
||||||
raise exception.UserParameterMissing(key=param_name)
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceRef(function.Function):
|
|
||||||
'''
|
|
||||||
A function for resolving resource references.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Ref" : "<resource_name>" }
|
|
||||||
'''
|
|
||||||
|
|
||||||
def _resource(self):
|
|
||||||
resource_name = function.resolve(self.args)
|
|
||||||
|
|
||||||
return self.stack[resource_name]
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
return self._resource().FnGetRefId()
|
|
||||||
|
|
||||||
|
|
||||||
def Ref(stack, fn_name, args):
|
|
||||||
'''
|
|
||||||
A function for resolving parameters or resource references.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Ref" : "<param_name>" }
|
|
||||||
|
|
||||||
or::
|
|
||||||
|
|
||||||
{ "Ref" : "<resource_name>" }
|
|
||||||
'''
|
|
||||||
if args in stack.t[stack.t.RESOURCES]:
|
|
||||||
RefClass = ResourceRef
|
|
||||||
else:
|
|
||||||
RefClass = ParamRef
|
|
||||||
return RefClass(stack, fn_name, args)
|
|
||||||
|
|
||||||
|
|
||||||
class GetAtt(function.Function):
|
|
||||||
'''
|
|
||||||
A function for resolving resource attributes.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Fn::GetAtt" : [ "<resource_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
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(_('Arguments to "%s" must be of the form '
|
|
||||||
'[resource_name, attribute]') % self.fn_name)
|
|
||||||
|
|
||||||
return resource_name, attribute
|
|
||||||
|
|
||||||
def _resource(self):
|
|
||||||
resource_name = function.resolve(self._resource_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return self.stack[resource_name]
|
|
||||||
except KeyError:
|
|
||||||
raise exception.InvalidTemplateAttribute(
|
|
||||||
resource=resource_name,
|
|
||||||
key=function.resolve(self._attribute))
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
attribute = function.resolve(self._attribute)
|
|
||||||
|
|
||||||
r = self._resource()
|
|
||||||
if (r.status in (r.IN_PROGRESS, r.COMPLETE) and
|
|
||||||
r.action in (r.CREATE, r.RESUME, r.UPDATE)):
|
|
||||||
return r.FnGetAtt(attribute)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class Select(function.Function):
|
|
||||||
'''
|
|
||||||
A function for selecting an item from a list or map.
|
|
||||||
|
|
||||||
Takes the form (for a list lookup)::
|
|
||||||
|
|
||||||
{ "Fn::Select" : [ "<index>", [ "<value_1>", "<value_2>", ... ] ] }
|
|
||||||
|
|
||||||
Takes the form (for a map lookup)::
|
|
||||||
|
|
||||||
{ "Fn::Select" : [ "<index>", { "<key_1>": "<value_1>", ... } ] }
|
|
||||||
|
|
||||||
If the selected index is not found, this function resolves to an empty
|
|
||||||
string.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, stack, fn_name, args):
|
|
||||||
super(Select, self).__init__(stack, fn_name, args)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._lookup, self._strings = self.args
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(_('Arguments to "%s" must be of the form '
|
|
||||||
'[index, collection]') % self.fn_name)
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
index = function.resolve(self._lookup)
|
|
||||||
|
|
||||||
try:
|
|
||||||
index = int(index)
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
strings = function.resolve(self._strings)
|
|
||||||
|
|
||||||
if strings == '':
|
|
||||||
# an empty string is a common response from other
|
|
||||||
# functions when result is not currently available.
|
|
||||||
# Handle by returning an empty string
|
|
||||||
return ''
|
|
||||||
|
|
||||||
if isinstance(strings, basestring):
|
|
||||||
# might be serialized json.
|
|
||||||
try:
|
|
||||||
strings = json.loads(strings)
|
|
||||||
except ValueError as json_ex:
|
|
||||||
fmt_data = {'fn_name': self.fn_name,
|
|
||||||
'err': json_ex}
|
|
||||||
raise ValueError(_('"%(fn_name)s": %(err)s') % fmt_data)
|
|
||||||
|
|
||||||
if isinstance(strings, collections.Mapping):
|
|
||||||
if not isinstance(index, basestring):
|
|
||||||
raise TypeError(_('Index to "%s" must be a string') %
|
|
||||||
self.fn_name)
|
|
||||||
return strings.get(index, '')
|
|
||||||
|
|
||||||
if (isinstance(strings, collections.Sequence) and
|
|
||||||
not isinstance(strings, basestring)):
|
|
||||||
if not isinstance(index, (int, long)):
|
|
||||||
raise TypeError(_('Index to "%s" must be an integer') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return strings[index]
|
|
||||||
except IndexError:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
if strings is None:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
raise TypeError(_('Arguments to %s not fully resolved') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
|
|
||||||
class Join(function.Function):
|
|
||||||
'''
|
|
||||||
A function for joining strings.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Fn::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 isinstance(self.args, (basestring, collections.Mapping)):
|
|
||||||
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 (isinstance(strings, basestring) 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, basestring):
|
|
||||||
raise TypeError(_('"%s" delimiter must be a string') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
def ensure_string(s):
|
|
||||||
if s is None:
|
|
||||||
return ''
|
|
||||||
if not isinstance(s, basestring):
|
|
||||||
raise TypeError(_('Items to join must be strings'))
|
|
||||||
return s
|
|
||||||
|
|
||||||
return delim.join(ensure_string(s) for s in strings)
|
|
||||||
|
|
||||||
|
|
||||||
class Split(function.Function):
|
|
||||||
'''
|
|
||||||
A function for splitting strings.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Fn::Split" : [ "<delim>", "<string_1><delim><string_2>..." ] }
|
|
||||||
|
|
||||||
And resolves to::
|
|
||||||
|
|
||||||
[ "<string_1>", "<string_2>", ... ]
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, stack, fn_name, args):
|
|
||||||
super(Split, self).__init__(stack, fn_name, args)
|
|
||||||
|
|
||||||
example = '"%s" : [ ",", "str1,str2"]]' % self.fn_name
|
|
||||||
fmt_data = {'fn_name': self.fn_name,
|
|
||||||
'example': example}
|
|
||||||
|
|
||||||
if isinstance(self.args, (basestring, collections.Mapping)):
|
|
||||||
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 not isinstance(self._delim, basestring):
|
|
||||||
raise TypeError(_("Delimiter for %s must be string") %
|
|
||||||
self.fn_name)
|
|
||||||
if not isinstance(strings, basestring):
|
|
||||||
raise TypeError(_("String to split must be string; got %s") %
|
|
||||||
type(strings))
|
|
||||||
|
|
||||||
return strings.split(self._delim)
|
|
||||||
|
|
||||||
|
|
||||||
class Replace(function.Function):
|
|
||||||
'''
|
|
||||||
A function for performing string subsitutions.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Fn::Replace" : [
|
|
||||||
{ "<key_1>": "<value_1>", "<key_2>": "<value_2>", ... },
|
|
||||||
"<key_1> <key_2>"
|
|
||||||
] }
|
|
||||||
|
|
||||||
And resolves to::
|
|
||||||
|
|
||||||
"<value_1> <value_2>"
|
|
||||||
|
|
||||||
This is implemented using python str.replace on each key. The order in
|
|
||||||
which replacements are performed is 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):
|
|
||||||
raise TypeError(_('"%s" parameters must be a mapping') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
def _parse_args(self):
|
|
||||||
|
|
||||||
example = ('{"%s": '
|
|
||||||
'[ {"$var1": "foo", "%%var2%%": "bar"}, '
|
|
||||||
'"$var1 is %%var2%%"]}' % self.fn_name)
|
|
||||||
fmt_data = {'fn_name': self.fn_name,
|
|
||||||
'example': example}
|
|
||||||
|
|
||||||
if isinstance(self.args, (basestring, collections.Mapping)):
|
|
||||||
raise TypeError(_('Incorrect arguments to "%(fn_name)s" '
|
|
||||||
'should be: %(example)s') % fmt_data)
|
|
||||||
|
|
||||||
try:
|
|
||||||
mapping, string = self.args
|
|
||||||
except ValueError:
|
|
||||||
raise ValueError(_('Incorrect arguments to "%(fn_name)s" '
|
|
||||||
'should be: %(example)s') % fmt_data)
|
|
||||||
else:
|
|
||||||
return mapping, string
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
template = function.resolve(self._string)
|
|
||||||
mapping = function.resolve(self._mapping)
|
|
||||||
|
|
||||||
if not isinstance(template, basestring):
|
|
||||||
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, basestring):
|
|
||||||
raise TypeError(_('"%s" param placeholders must be strings') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
if value is None:
|
|
||||||
value = ''
|
|
||||||
|
|
||||||
if not isinstance(value, (basestring, int, long, float, bool)):
|
|
||||||
raise TypeError(_('"%s" params must be strings or numbers') %
|
|
||||||
self.fn_name)
|
|
||||||
|
|
||||||
return string.replace(placeholder, unicode(value))
|
|
||||||
|
|
||||||
return reduce(replace, mapping.iteritems(), template)
|
|
||||||
|
|
||||||
|
|
||||||
class Base64(function.Function):
|
|
||||||
'''
|
|
||||||
A placeholder function for converting to base64.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Fn::Base64" : "<string>" }
|
|
||||||
|
|
||||||
This function actually performs no conversion. It is included for the
|
|
||||||
benefit of templates that convert UserData to Base64. Heat accepts UserData
|
|
||||||
in plain text.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
resolved = function.resolve(self.args)
|
|
||||||
if not isinstance(resolved, basestring):
|
|
||||||
raise TypeError(_('"%s" argument must be a string') % self.fn_name)
|
|
||||||
return resolved
|
|
||||||
|
|
||||||
|
|
||||||
class MemberListToMap(function.Function):
|
|
||||||
'''
|
|
||||||
A function for converting lists containing enumerated keys and values to
|
|
||||||
a mapping.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ 'Fn::MemberListToMap' : [ 'Name',
|
|
||||||
'Value',
|
|
||||||
[ '.member.0.Name=<key_0>',
|
|
||||||
'.member.0.Value=<value_0>',
|
|
||||||
... ] ] }
|
|
||||||
|
|
||||||
And resolves to::
|
|
||||||
|
|
||||||
{ "<key_0>" : "<value_0>", ... }
|
|
||||||
|
|
||||||
The first two arguments are the names of the key and value.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, stack, fn_name, args):
|
|
||||||
super(MemberListToMap, self).__init__(stack, fn_name, args)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._keyname, self._valuename, self._list = self.args
|
|
||||||
except ValueError:
|
|
||||||
correct = '''
|
|
||||||
{'Fn::MemberListToMap': ['Name', 'Value',
|
|
||||||
['.member.0.Name=key',
|
|
||||||
'.member.0.Value=door']]}
|
|
||||||
'''
|
|
||||||
raise TypeError(_('Wrong Arguments try: "%s"') % correct)
|
|
||||||
|
|
||||||
if not isinstance(self._keyname, basestring):
|
|
||||||
raise TypeError(_('%s Key Name must be a string') % self.fn_name)
|
|
||||||
|
|
||||||
if not isinstance(self._valuename, basestring):
|
|
||||||
raise TypeError(_('%s Value Name must be a string') % self.fn_name)
|
|
||||||
|
|
||||||
def result(self):
|
|
||||||
member_list = function.resolve(self._list)
|
|
||||||
|
|
||||||
if not isinstance(member_list, collections.Iterable):
|
|
||||||
raise TypeError(_('Member list must be a list'))
|
|
||||||
|
|
||||||
def item(s):
|
|
||||||
if not isinstance(s, basestring):
|
|
||||||
raise TypeError(_("Member list items must be strings"))
|
|
||||||
return s.split('=', 1)
|
|
||||||
|
|
||||||
partials = dict(item(s) for s in member_list)
|
|
||||||
return aws_utils.extract_param_pairs(partials,
|
|
||||||
prefix='',
|
|
||||||
keyname=self._keyname,
|
|
||||||
valuename=self._valuename)
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceFacade(function.Function):
|
|
||||||
'''
|
|
||||||
A function for obtaining data from the facade resource from within the
|
|
||||||
corresponding provider template.
|
|
||||||
|
|
||||||
Takes the form::
|
|
||||||
|
|
||||||
{ "Fn::ResourceFacade": "<attribute_type>" }
|
|
||||||
|
|
||||||
where the valid attribute types are "Metadata", "DeletionPolicy" and
|
|
||||||
"UpdatePolicy".
|
|
||||||
'''
|
|
||||||
|
|
||||||
_RESOURCE_ATTRIBUTES = (
|
|
||||||
METADATA, DELETION_POLICY, UPDATE_POLICY,
|
|
||||||
) = (
|
|
||||||
'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
|
|
||||||
elif attr == self.UPDATE_POLICY:
|
|
||||||
return self.stack.parent_resource.t.get(attr, {})
|
|
||||||
elif attr == self.DELETION_POLICY:
|
|
||||||
try:
|
|
||||||
return self.stack.parent_resource.t[attr]
|
|
||||||
except KeyError:
|
|
||||||
# TODO(zaneb): This should have a default!
|
|
||||||
fmt_data = {'fn_name': self.fn_name,
|
|
||||||
'key': attr}
|
|
||||||
raise KeyError(_('"%(fn_name)s" '
|
|
||||||
'key "%(key)s" not found') % fmt_data)
|
|
||||||
|
@ -32,6 +32,8 @@ from heat.engine import parser
|
|||||||
from heat.engine import scheduler
|
from heat.engine import scheduler
|
||||||
from heat.engine import template
|
from heat.engine import template
|
||||||
|
|
||||||
|
import heat.engine.cfn.functions
|
||||||
|
|
||||||
from heat.tests.fakes import FakeKeystoneClient
|
from heat.tests.fakes import FakeKeystoneClient
|
||||||
from heat.tests.common import HeatTestCase
|
from heat.tests.common import HeatTestCase
|
||||||
from heat.tests import utils
|
from heat.tests import utils
|
||||||
@ -260,7 +262,7 @@ Mappings:
|
|||||||
|
|
||||||
p_snippet = {"Ref": "baz"}
|
p_snippet = {"Ref": "baz"}
|
||||||
parsed = tmpl.parse(stack, p_snippet)
|
parsed = tmpl.parse(stack, p_snippet)
|
||||||
self.assertTrue(isinstance(parsed, template.ParamRef))
|
self.assertTrue(isinstance(parsed, heat.engine.cfn.functions.ParamRef))
|
||||||
|
|
||||||
def test_select_from_list(self):
|
def test_select_from_list(self):
|
||||||
tmpl = parser.Template(empty_template)
|
tmpl = parser.Template(empty_template)
|
||||||
|
Loading…
Reference in New Issue
Block a user