Implement HOT intrinsic function get_file
This function takes a single string argument and uses that as the key in a lookup into the template files. The resulting content is substituted as a chunk of text. The files keys can be arbitrary and the files section can be built manually when generating the API request. However by convention heatclient will use the absolute URL of the file as the key. Change-Id: I83a73b14e1fbf56545151da1d8a0d7afe89149a4
This commit is contained in:
parent
ba0f1afd8b
commit
0cc40facca
|
@ -612,3 +612,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*.
|
||||
|
|
|
@ -253,6 +253,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))
|
||||
|
|
|
@ -752,7 +752,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):
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -205,6 +205,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
|
||||
|
|
Loading…
Reference in New Issue