mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Support Jinja imports with relative paths. Issue #13889
Jinja imports will be interpreted as originating from the top of each of the directories in the searchpath when the template name does not begin with './' or '../'. When a template name begins with './' or '../' then the import will be relative to the importing file. Prior practices required the following construct: .. code-block:: yaml {% from tpldir ~ '/foo' import bar %} This patch supports a more "natural" construct: .. code-block:: yaml {% from './foo' import bar %} Comparatively when importing from a parent directory - prior practice: .. code-block:: yaml {% from tpldir ~ '/../foo' import bar %} Construct supported by this patch: .. code-block:: yaml {% from '../foo' import bar %}
This commit is contained in:
parent
6147f08df7
commit
485e151507
7 changed files with 70 additions and 13 deletions
|
@ -98,20 +98,52 @@ class SaltCacheLoader(BaseLoader):
|
|||
self.cached.append(template)
|
||||
|
||||
def get_source(self, environment, template):
|
||||
# checks for relative '..' paths
|
||||
if '..' in template:
|
||||
log.warning(
|
||||
'Discarded template path \'%s\', relative paths are '
|
||||
'prohibited', template
|
||||
)
|
||||
raise TemplateNotFound(template)
|
||||
'''
|
||||
Salt-specific loader to find imported jinja files.
|
||||
|
||||
self.check_cache(template)
|
||||
Jinja imports will be interpreted as originating from the top
|
||||
of each of the directories in the searchpath when the template
|
||||
name does not begin with './' or '../'. When a template name
|
||||
begins with './' or '../' then the import will be relative to
|
||||
the importing file.
|
||||
|
||||
'''
|
||||
# FIXME: somewhere do seprataor replacement: '\\' => '/'
|
||||
_template = template
|
||||
if template.split('/', 1)[0] in ('..', '.'):
|
||||
is_relative = True
|
||||
else:
|
||||
is_relative = False
|
||||
# checks for relative '..' paths that step-out of file_roots
|
||||
if is_relative:
|
||||
# Starts with a relative path indicator
|
||||
|
||||
if not environment or 'tpldir' not in environment.globals:
|
||||
log.warning(
|
||||
'Relative path "%s" cannot be resolved without an environment',
|
||||
template
|
||||
)
|
||||
print('Relative path "{0}" cannot be resolved without an environment'.format(template))
|
||||
raise TemplateNotFound
|
||||
base_path = environment.globals['tpldir']
|
||||
_template = os.path.normpath('/'.join((base_path, _template)))
|
||||
if _template.split('/', 1)[0] == '..':
|
||||
log.warning(
|
||||
'Discarded template path "%s": attempts to'
|
||||
' ascend outside of file roots', template
|
||||
)
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
self.check_cache(_template)
|
||||
|
||||
if environment and template:
|
||||
tpldir = os.path.dirname(template).replace('\\', '/')
|
||||
tpldir = os.path.dirname(_template).replace('\\', '/')
|
||||
tplfile = _template
|
||||
if is_relative:
|
||||
tpldir = environment.globals.get('tpldir', tpldir)
|
||||
tplfile = template
|
||||
tpldata = {
|
||||
'tplfile': template,
|
||||
'tplfile': tplfile,
|
||||
'tpldir': '.' if tpldir == '' else tpldir,
|
||||
'tpldot': tpldir.replace('/', '.'),
|
||||
}
|
||||
|
@ -119,7 +151,7 @@ class SaltCacheLoader(BaseLoader):
|
|||
|
||||
# pylint: disable=cell-var-from-loop
|
||||
for spath in self.searchpath:
|
||||
filepath = os.path.join(spath, template)
|
||||
filepath = os.path.join(spath, _template)
|
||||
try:
|
||||
with salt.utils.files.fopen(filepath, 'rb') as ifile:
|
||||
contents = ifile.read().decode(self.encoding)
|
||||
|
@ -205,7 +237,7 @@ def skip_filter(data):
|
|||
'''
|
||||
Suppress data output
|
||||
|
||||
.. code-balock:: yaml
|
||||
.. code-block:: yaml
|
||||
|
||||
{% my_string = "foo" %}
|
||||
|
||||
|
|
|
@ -332,7 +332,7 @@ class SaltTestingParser(optparse.OptionParser):
|
|||
os.path.basename(fpath).startswith('test_'):
|
||||
self.options.name.append(fpath)
|
||||
continue
|
||||
self.exit(status=1, msg='\'{}\' is not a valid test module'.format(fpath))
|
||||
self.exit(status=1, msg='\'{}\' is not a valid test module\n'.format(fpath))
|
||||
|
||||
print_header(u'', inline=True, width=self.options.output_columns)
|
||||
self.pre_execution_cleanup()
|
||||
|
|
1
tests/unit/templates/files/rescape
Normal file
1
tests/unit/templates/files/rescape
Normal file
|
@ -0,0 +1 @@
|
|||
FAILURE
|
1
tests/unit/templates/files/test/relative/rescape
Normal file
1
tests/unit/templates/files/test/relative/rescape
Normal file
|
@ -0,0 +1 @@
|
|||
{% import '../../rescape' as xfail -%}
|
2
tests/unit/templates/files/test/relative/rhello
Normal file
2
tests/unit/templates/files/test/relative/rhello
Normal file
|
@ -0,0 +1,2 @@
|
|||
{% from './rmacro' import rmacro with context -%}
|
||||
{{ rmacro('Hey') ~ rmacro(a|default('a'), b|default('b')) }}
|
4
tests/unit/templates/files/test/relative/rmacro
Normal file
4
tests/unit/templates/files/test/relative/rmacro
Normal file
|
@ -0,0 +1,4 @@
|
|||
{% from '../macro' import mymacro with context %}
|
||||
{% macro rmacro(greeting, greetee='world') -%}
|
||||
{{ mymacro(greeting, greetee) }}
|
||||
{%- endmacro %}
|
|
@ -170,6 +170,23 @@ class TestSaltCacheLoader(TestCase):
|
|||
self.assertEqual(fc.requests[0]['path'], 'salt://hello_import')
|
||||
self.assertEqual(fc.requests[1]['path'], 'salt://macro')
|
||||
|
||||
def test_relative_import(self):
|
||||
'''
|
||||
You can import using relative paths
|
||||
issue-13889
|
||||
'''
|
||||
fc, jinja = self.get_test_saltenv()
|
||||
tmpl = jinja.get_template('relative/rhello')
|
||||
result = tmpl.render()
|
||||
self.assertEqual(result, 'Hey world !a b !')
|
||||
assert len(fc.requests) == 3
|
||||
self.assertEqual(fc.requests[0]['path'], 'salt://relative/rhello')
|
||||
self.assertEqual(fc.requests[1]['path'], 'salt://relative/rmacro')
|
||||
self.assertEqual(fc.requests[2]['path'], 'salt://macro')
|
||||
# This must fail when rendered: attempts to import from outside file root
|
||||
template = jinja.get_template('relative/rescape')
|
||||
self.assertRaises(exceptions.TemplateNotFound, template.render)
|
||||
|
||||
def test_include(self):
|
||||
'''
|
||||
You can also include a template that imports and uses macros
|
||||
|
|
Loading…
Add table
Reference in a new issue