Source code for heat.engine.cfn.functions

#
#    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 itertools

from oslo_serialization import jsonutils
import six

from heat.api.aws import utils as aws_utils
from heat.common import exception
from heat.common.i18n import _
from heat.engine import function


[docs]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(six.text_type(ex))
[docs] 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]
[docs]class GetAZs(function.Function): """A function for retrieving the availability zones. Takes the form:: { "Fn::GetAZs" : "<region>" } """
[docs] def result(self): # TODO(therve): Implement region scoping if self.stack is None: return ['nova'] else: return self.stack.get_availability_zones()
[docs]class ParamRef(function.Function): """A function for resolving parameter references. Takes the form:: { "Ref" : "<param_name>" } """ def __init__(self, stack, fn_name, args): super(ParamRef, self).__init__(stack, fn_name, args) self.parameters = self.stack.parameters
[docs] def result(self): param_name = function.resolve(self.args) try: return self.parameters[param_name] except KeyError: raise exception.InvalidTemplateReference(resource=param_name, key='unknown')
[docs]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)
[docs] def dependencies(self, path): return itertools.chain(super(ResourceRef, self).dependencies(path), [self._resource(path)])
[docs] def result(self): return self._resource().FnGetRefId()
[docs]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: RefClass = ResourceRef else: RefClass = ParamRef return RefClass(stack, fn_name, args)
[docs]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, 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)
[docs] 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)
[docs] def dependencies(self, path): return itertools.chain(super(GetAtt, self).dependencies(path), [self._resource(path)])
def _allow_without_attribute_name(self): return False
[docs] 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 six.iterkeys(res.attributes_schema)): raise exception.InvalidTemplateAttribute( resource=self._resource_name, key=attr)
[docs] 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
[docs]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)
[docs] def result(self): index = function.resolve(self._lookup) 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, six.string_types): # might be serialized json. try: strings = jsonutils.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, six.string_types): raise TypeError(_('Index to "%s" must be a string') % self.fn_name) return strings.get(index, '') try: index = int(index) except (ValueError, TypeError): pass if (isinstance(strings, collections.Sequence) and not isinstance(strings, six.string_types)): if not isinstance(index, six.integer_types): 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)
[docs]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 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)
[docs] 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)
[docs]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, (six.string_types, 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)
[docs] def result(self): strings = function.resolve(self._strings) if not isinstance(self._delim, six.string_types): raise TypeError(_("Delimiter for %s must be string") % self.fn_name) if not isinstance(strings, six.string_types): raise TypeError(_("String to split must be string; got %s") % type(strings)) return strings.split(self._delim)
[docs]class Replace(function.Function): """A function for performing string substitutions. 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. Longer keys are substituted before shorter ones, but the order in which replacements are 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": ' '[ {"$var1": "foo", "%%var2%%": "bar"}, ' '"$var1 is %%var2%%"]}' % self.fn_name) fmt_data = {'fn_name': self.fn_name, 'example': example} if isinstance(self.args, (six.string_types, 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
[docs] 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)
[docs]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. """
[docs] def result(self): resolved = function.resolve(self.args) if not isinstance(resolved, six.string_types): raise TypeError(_('"%s" argument must be a string') % self.fn_name) return resolved
[docs]class MemberListToMap(function.Function): """A function to convert lists with enumerated keys and values to 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, six.string_types): raise TypeError(_('%s Key Name must be a string') % self.fn_name) if not isinstance(self._valuename, six.string_types): raise TypeError(_('%s Value Name must be a string') % self.fn_name)
[docs] 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, six.string_types): 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)
[docs]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 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)
[docs] 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.get('UpdatePolicy', {}) return function.resolve(up) elif attr == self.DELETION_POLICY: dp = self.stack.parent_resource.t.deletion_policy() return function.resolve(dp)

Project Source