Merge "Implement HOT intrinsic function get_file"

This commit is contained in:
Jenkins 2014-02-13 13:03:33 +00:00 committed by Gerrit Code Review
commit e00c41b48d
5 changed files with 128 additions and 1 deletions

View File

@ -637,3 +637,45 @@ In the example above, one can imagine that MySQL is being configured on a
compute instance and the root password is going to be set based on a user
provided parameter. The script for doing this is provided as userdata to the
compute instance, leveraging the str_replace function.
get_file
------------
The *get_file* function allows string content to be substituted into the
template. It is generally used as a file inclusion mechanism for files
containing non-heat scripts or configuration files.
The syntax of the get_file function is as follows:
::
get_file: <content key>
The *content key* will be used to look up the files dictionary that is
provided in the REST API call. The *heat* client command from
python-heatclient is *get_file* aware and will populate the *files* with
the actual content of fetched paths and URLs. The *heat* client command
supports relative paths and will transform these to absolute URLs which
will be used as the *content key* in the files dictionary.
The example below demonstrates *get_file* usage with both relative and
absolute URLs.
::
resources:
my_instance:
type: OS::Nova::Server
properties:
# general properties ...
user_data:
get_file: my_instance_user_data.sh
my_other_instance:
type: OS::Nova::Server
properties:
# general properties ...
user_data:
get_file: http://example.com/my_other_instance_user_data.sh
If this template was launched from a local file this would result in
a *files* dictionary containing entries with keys
*file:///path/to/my_instance_user_data.sh* and
*http://example.com/my_other_instance_user_data.sh*.

View File

@ -288,6 +288,32 @@ class HOTemplate(template.Template):
return template._resolve(match_str_replace,
handle_str_replace, s, transform)
def resolve_get_file(self, s, transform=None):
"""
Resolve file inclusion via function get_file. For any key provided
the contents of the value in the template files dictionary
will be substituted.
Resolves the get_file function of the form::
get_file:
<string key>
"""
def handle_get_file(args):
if not (isinstance(args, basestring)):
raise TypeError(
_('Argument to "get_file" must be a string'))
f = self.files.get(args)
if f is None:
raise ValueError(_('No content found in the "files" section '
'for get_file path: %s') % args)
return f
match_get_file = lambda k, v: k == 'get_file'
return template._resolve(match_get_file,
handle_get_file, s, transform)
def param_schemata(self):
params = self.t.get(self.PARAMETERS, {}).iteritems()
return dict((name, HOTParamSchema.from_dict(schema))

View File

@ -770,7 +770,8 @@ def resolve_static_data(template, stack, parameters, snippet):
functools.partial(template.resolve_resource_facade,
stack=stack),
template.resolve_find_in_map,
template.reduce_joins])
template.reduce_joins,
template.resolve_get_file])
def resolve_runtime_data(template, resources, snippet):

View File

@ -481,6 +481,13 @@ class Template(collections.Mapping):
handle_resource_facade,
s, transform)
@staticmethod
def resolve_get_file(s, transform=None):
# cfn templates do not have any analog to get_file so this function
# should remain not implemented. Attempts to use get_file in a cfn
# template will be passed through with no modification.
return s
def param_schemata(self):
params = self.t.get(self.PARAMETERS, {}).iteritems()
return dict((name, parameters.Schema.from_dict(schema))

View File

@ -187,6 +187,57 @@ class HOTemplateTest(HeatTestCase):
self.assertRaises(TypeError, tmpl.resolve_replace, snippet)
def test_get_file(self):
"""Test get_file function."""
snippet = {'get_file': 'file:///tmp/foo.yaml'}
snippet_resolved = 'foo contents'
tmpl = parser.Template(hot_tpl_empty, files={
'file:///tmp/foo.yaml': 'foo contents'
})
self.assertEqual(snippet_resolved, tmpl.resolve_get_file(snippet))
def test_get_file_not_string(self):
"""Test get_file function with non-string argument."""
snippet = {'get_file': ['file:///tmp/foo.yaml']}
tmpl = parser.Template(hot_tpl_empty)
notStrErr = self.assertRaises(
TypeError, tmpl.resolve_get_file, snippet)
self.assertEqual(
'Argument to "get_file" must be a string',
str(notStrErr))
def test_get_file_missing_files(self):
"""Test get_file function with no matching key in files section."""
snippet = {'get_file': 'file:///tmp/foo.yaml'}
tmpl = parser.Template(hot_tpl_empty, files={
'file:///tmp/bar.yaml': 'bar contents'
})
missingErr = self.assertRaises(
ValueError, tmpl.resolve_get_file, snippet)
self.assertEqual(
('No content found in the "files" section for '
'get_file path: file:///tmp/foo.yaml'),
str(missingErr))
def test_get_file_nested_does_not_resolve(self):
"""Test get_file function does not resolve nested calls."""
snippet = {'get_file': 'file:///tmp/foo.yaml'}
snippet_resolved = '{get_file: file:///tmp/bar.yaml}'
tmpl = parser.Template(hot_tpl_empty, files={
'file:///tmp/foo.yaml': snippet_resolved,
'file:///tmp/bar.yaml': 'bar content',
})
self.assertEqual(snippet_resolved, tmpl.resolve_get_file(snippet))
def test_prevent_parameters_access(self):
"""
Test that the parameters section can't be accesed using the template