mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-06-29 11:40:22 -07:00
parent
95882faca6
commit
728fce0c44
8 changed files with 90 additions and 47 deletions
5
changelogs/fragments/faster-is-template.yaml
Normal file
5
changelogs/fragments/faster-is-template.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
minor_changes:
|
||||||
|
- Templar - Speed up ``is_template`` by lexing the string, instead of actually
|
||||||
|
templating the string (https://github.com/ansible/ansible/pull/57489)
|
||||||
|
- InventoryManager - Speed up host subset calculation by performing direct host
|
||||||
|
uuid comparisons, instead of Host object comparisons
|
|
@ -231,7 +231,7 @@ class TaskExecutor:
|
||||||
loop_terms = listify_lookup_plugin_terms(terms=self._task.loop, templar=templar, loader=self._loader, fail_on_undefined=fail,
|
loop_terms = listify_lookup_plugin_terms(terms=self._task.loop, templar=templar, loader=self._loader, fail_on_undefined=fail,
|
||||||
convert_bare=False)
|
convert_bare=False)
|
||||||
if not fail:
|
if not fail:
|
||||||
loop_terms = [t for t in loop_terms if not templar._contains_vars(t)]
|
loop_terms = [t for t in loop_terms if not templar.is_template(t)]
|
||||||
|
|
||||||
# get lookup
|
# get lookup
|
||||||
mylookup = self._shared_loader_obj.lookup_loader.get(self._task.loop_with, loader=self._loader, templar=templar)
|
mylookup = self._shared_loader_obj.lookup_loader.get(self._task.loop_with, loader=self._loader, templar=templar)
|
||||||
|
@ -427,7 +427,7 @@ class TaskExecutor:
|
||||||
# with_items loop) so don't make the templated string permanent yet.
|
# with_items loop) so don't make the templated string permanent yet.
|
||||||
templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=variables)
|
templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=variables)
|
||||||
task_action = self._task.action
|
task_action = self._task.action
|
||||||
if templar._contains_vars(task_action):
|
if templar.is_template(task_action):
|
||||||
task_action = templar.template(task_action, fail_on_undefined=False)
|
task_action = templar.template(task_action, fail_on_undefined=False)
|
||||||
|
|
||||||
if len(items) > 0 and task_action in self.SQUASH_ACTIONS:
|
if len(items) > 0 and task_action in self.SQUASH_ACTIONS:
|
||||||
|
@ -445,7 +445,7 @@ class TaskExecutor:
|
||||||
# contains a template that we can squash for
|
# contains a template that we can squash for
|
||||||
template_no_item = template_with_item = None
|
template_no_item = template_with_item = None
|
||||||
if name:
|
if name:
|
||||||
if templar._contains_vars(name):
|
if templar.is_template(name):
|
||||||
variables[loop_var] = '\0$'
|
variables[loop_var] = '\0$'
|
||||||
template_no_item = templar.template(name, variables, cache=False)
|
template_no_item = templar.template(name, variables, cache=False)
|
||||||
variables[loop_var] = '\0@'
|
variables[loop_var] = '\0@'
|
||||||
|
|
|
@ -360,8 +360,8 @@ class InventoryManager(object):
|
||||||
# mainly useful for hostvars[host] access
|
# mainly useful for hostvars[host] access
|
||||||
if not ignore_limits and self._subset:
|
if not ignore_limits and self._subset:
|
||||||
# exclude hosts not in a subset, if defined
|
# exclude hosts not in a subset, if defined
|
||||||
subset = self._evaluate_patterns(self._subset)
|
subset_uuids = [s._uuid for s in self._evaluate_patterns(self._subset)]
|
||||||
hosts = [h for h in hosts if h in subset]
|
hosts = [h for h in hosts if h._uuid in subset_uuids]
|
||||||
|
|
||||||
if not ignore_restrictions and self._restriction:
|
if not ignore_restrictions and self._restriction:
|
||||||
# exclude hosts mentioned in any restriction (ex: failed hosts)
|
# exclude hosts mentioned in any restriction (ex: failed hosts)
|
||||||
|
|
|
@ -144,7 +144,7 @@ class ModuleArgsParser:
|
||||||
if additional_args:
|
if additional_args:
|
||||||
if isinstance(additional_args, string_types):
|
if isinstance(additional_args, string_types):
|
||||||
templar = Templar(loader=None)
|
templar = Templar(loader=None)
|
||||||
if templar._contains_vars(additional_args):
|
if templar.is_template(additional_args):
|
||||||
final_args['_variable_params'] = additional_args
|
final_args['_variable_params'] = additional_args
|
||||||
else:
|
else:
|
||||||
raise AnsibleParserError("Complex args containing variables cannot use bare variables (without Jinja2 delimiters), "
|
raise AnsibleParserError("Complex args containing variables cannot use bare variables (without Jinja2 delimiters), "
|
||||||
|
@ -311,7 +311,7 @@ class ModuleArgsParser:
|
||||||
elif args.get('_raw_params', '') != '' and action not in RAW_PARAM_MODULES:
|
elif args.get('_raw_params', '') != '' and action not in RAW_PARAM_MODULES:
|
||||||
templar = Templar(loader=None)
|
templar = Templar(loader=None)
|
||||||
raw_params = args.pop('_raw_params')
|
raw_params = args.pop('_raw_params')
|
||||||
if templar._contains_vars(raw_params):
|
if templar.is_template(raw_params):
|
||||||
args['_variable_params'] = raw_params
|
args['_variable_params'] = raw_params
|
||||||
else:
|
else:
|
||||||
raise AnsibleParserError("this task '%s' has extra params, which is only allowed in the following modules: %s" % (action,
|
raise AnsibleParserError("this task '%s' has extra params, which is only allowed in the following modules: %s" % (action,
|
||||||
|
|
|
@ -164,7 +164,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
|
||||||
else:
|
else:
|
||||||
is_static = C.DEFAULT_TASK_INCLUDES_STATIC or \
|
is_static = C.DEFAULT_TASK_INCLUDES_STATIC or \
|
||||||
(use_handlers and C.DEFAULT_HANDLER_INCLUDES_STATIC) or \
|
(use_handlers and C.DEFAULT_HANDLER_INCLUDES_STATIC) or \
|
||||||
(not templar._contains_vars(t.args['_raw_params']) and t.all_parents_static() and not t.loop)
|
(not templar.is_template(t.args['_raw_params']) and t.all_parents_static() and not t.loop)
|
||||||
|
|
||||||
if is_static:
|
if is_static:
|
||||||
if t.loop is not None:
|
if t.loop is not None:
|
||||||
|
@ -351,7 +351,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
|
||||||
# template the role name now, if needed
|
# template the role name now, if needed
|
||||||
all_vars = variable_manager.get_vars(play=play, task=ir)
|
all_vars = variable_manager.get_vars(play=play, task=ir)
|
||||||
templar = Templar(loader=loader, variables=all_vars)
|
templar = Templar(loader=loader, variables=all_vars)
|
||||||
if templar._contains_vars(ir._role_name):
|
if templar.is_template(ir._role_name):
|
||||||
ir._role_name = templar.template(ir._role_name)
|
ir._role_name = templar.template(ir._role_name)
|
||||||
|
|
||||||
# uses compiled list from object
|
# uses compiled list from object
|
||||||
|
|
|
@ -129,7 +129,7 @@ class RoleDefinition(Base, Conditional, Taggable, CollectionSearch):
|
||||||
if self._variable_manager:
|
if self._variable_manager:
|
||||||
all_vars = self._variable_manager.get_vars(play=self._play)
|
all_vars = self._variable_manager.get_vars(play=self._play)
|
||||||
templar = Templar(loader=self._loader, variables=all_vars)
|
templar = Templar(loader=self._loader, variables=all_vars)
|
||||||
if templar._contains_vars(role_name):
|
if templar.is_template(role_name):
|
||||||
role_name = templar.template(role_name)
|
role_name = templar.template(role_name)
|
||||||
|
|
||||||
return role_name
|
return role_name
|
||||||
|
|
|
@ -88,6 +88,10 @@ else:
|
||||||
from jinja2.utils import concat as j2_concat
|
from jinja2.utils import concat as j2_concat
|
||||||
|
|
||||||
|
|
||||||
|
JINJA2_BEGIN_TOKENS = frozenset(('variable_begin', 'block_begin', 'comment_begin', 'raw_begin'))
|
||||||
|
JINJA2_END_TOKENS = frozenset(('variable_end', 'block_end', 'comment_end', 'raw_end'))
|
||||||
|
|
||||||
|
|
||||||
def generate_ansible_template_vars(path, dest_path=None):
|
def generate_ansible_template_vars(path, dest_path=None):
|
||||||
b_path = to_bytes(path)
|
b_path = to_bytes(path)
|
||||||
try:
|
try:
|
||||||
|
@ -159,6 +163,39 @@ def _escape_backslashes(data, jinja_env):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def is_template(data, jinja_env):
|
||||||
|
"""This function attempts to quickly detect whether a value is a jinja2
|
||||||
|
template. To do so, we look for the first 2 matching jinja2 tokens for
|
||||||
|
start and end delimiters.
|
||||||
|
"""
|
||||||
|
found = None
|
||||||
|
start = True
|
||||||
|
comment = False
|
||||||
|
d2 = jinja_env.preprocess(data)
|
||||||
|
|
||||||
|
# This wraps a lot of code, but this is due to lex returing a generator
|
||||||
|
# so we may get an exception at any part of the loop
|
||||||
|
try:
|
||||||
|
for token in jinja_env.lex(d2):
|
||||||
|
if token[1] in JINJA2_BEGIN_TOKENS:
|
||||||
|
if start and token[1] == 'comment_begin':
|
||||||
|
# Comments can wrap other token types
|
||||||
|
comment = True
|
||||||
|
start = False
|
||||||
|
# Example: variable_end -> variable
|
||||||
|
found = token[1].split('_')[0]
|
||||||
|
elif token[1] in JINJA2_END_TOKENS:
|
||||||
|
if token[1].split('_')[0] == found:
|
||||||
|
return True
|
||||||
|
elif comment:
|
||||||
|
continue
|
||||||
|
return False
|
||||||
|
except TemplateSyntaxError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _count_newlines_from_end(in_str):
|
def _count_newlines_from_end(in_str):
|
||||||
'''
|
'''
|
||||||
Counts the number of newlines at the end of a string. This is used during
|
Counts the number of newlines at the end of a string. This is used during
|
||||||
|
@ -498,7 +535,7 @@ class Templar:
|
||||||
if isinstance(variable, string_types):
|
if isinstance(variable, string_types):
|
||||||
result = variable
|
result = variable
|
||||||
|
|
||||||
if self._contains_vars(variable):
|
if self.is_template(variable):
|
||||||
# Check to see if the string we are trying to render is just referencing a single
|
# Check to see if the string we are trying to render is just referencing a single
|
||||||
# var. In this case we don't want to accidentally change the type of the variable
|
# var. In this case we don't want to accidentally change the type of the variable
|
||||||
# to a string by using the jinja template renderer. We just want to pass it.
|
# to a string by using the jinja template renderer. We just want to pass it.
|
||||||
|
@ -596,13 +633,7 @@ class Templar:
|
||||||
def is_template(self, data):
|
def is_template(self, data):
|
||||||
''' lets us know if data has a template'''
|
''' lets us know if data has a template'''
|
||||||
if isinstance(data, string_types):
|
if isinstance(data, string_types):
|
||||||
try:
|
return is_template(data, self.environment)
|
||||||
new = self.do_template(data, fail_on_undefined=True)
|
|
||||||
except (AnsibleUndefinedVariable, UndefinedError):
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
return (new != data)
|
|
||||||
elif isinstance(data, (list, tuple)):
|
elif isinstance(data, (list, tuple)):
|
||||||
for v in data:
|
for v in data:
|
||||||
if self.is_template(v):
|
if self.is_template(v):
|
||||||
|
@ -613,26 +644,7 @@ class Templar:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def templatable(self, data):
|
templatable = _contains_vars = is_template
|
||||||
'''
|
|
||||||
returns True if the data can be templated w/o errors
|
|
||||||
'''
|
|
||||||
templatable = True
|
|
||||||
try:
|
|
||||||
self.template(data)
|
|
||||||
except Exception:
|
|
||||||
templatable = False
|
|
||||||
return templatable
|
|
||||||
|
|
||||||
def _contains_vars(self, data):
|
|
||||||
'''
|
|
||||||
returns True if the data contains a variable pattern
|
|
||||||
'''
|
|
||||||
if isinstance(data, string_types):
|
|
||||||
for marker in (self.environment.block_start_string, self.environment.variable_start_string, self.environment.comment_start_string):
|
|
||||||
if marker in data:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _convert_bare_variable(self, variable):
|
def _convert_bare_variable(self, variable):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -104,17 +104,43 @@ class TestTemplarTemplate(BaseTemplar, unittest.TestCase):
|
||||||
# self.assertEqual(res['{{ a_keyword }}'], "blip")
|
# self.assertEqual(res['{{ a_keyword }}'], "blip")
|
||||||
print(res)
|
print(res)
|
||||||
|
|
||||||
def test_templatable(self):
|
def test_is_template_true(self):
|
||||||
res = self.templar.templatable('foo')
|
tests = [
|
||||||
self.assertTrue(res)
|
'{{ foo }}',
|
||||||
|
'{% foo %}',
|
||||||
|
'{# foo #}',
|
||||||
|
'{# {{ foo }} #}',
|
||||||
|
'{# {{ nothing }} {# #}',
|
||||||
|
'{# {{ nothing }} {# #} #}',
|
||||||
|
'{% raw %}{{ foo }}{% endraw %}',
|
||||||
|
]
|
||||||
|
for test in tests:
|
||||||
|
self.assertTrue(self.templar.is_template(test))
|
||||||
|
|
||||||
def test_templatable_none(self):
|
def test_is_template_false(self):
|
||||||
res = self.templar.templatable(None)
|
tests = [
|
||||||
self.assertTrue(res)
|
'foo',
|
||||||
|
'{{ foo',
|
||||||
|
'{% foo',
|
||||||
|
'{# foo',
|
||||||
|
'{{ foo %}',
|
||||||
|
'{{ foo #}',
|
||||||
|
'{% foo }}',
|
||||||
|
'{% foo #}',
|
||||||
|
'{# foo %}',
|
||||||
|
'{# foo }}',
|
||||||
|
'{{ foo {{',
|
||||||
|
'{% raw %}{% foo %}',
|
||||||
|
]
|
||||||
|
for test in tests:
|
||||||
|
self.assertFalse(self.templar.is_template(test))
|
||||||
|
|
||||||
@patch('ansible.template.Templar.template', side_effect=AnsibleError)
|
def test_is_template_raw_string(self):
|
||||||
def test_templatable_exception(self, mock_template):
|
res = self.templar.is_template('foo')
|
||||||
res = self.templar.templatable('foo')
|
self.assertFalse(res)
|
||||||
|
|
||||||
|
def test_is_template_none(self):
|
||||||
|
res = self.templar.is_template(None)
|
||||||
self.assertFalse(res)
|
self.assertFalse(res)
|
||||||
|
|
||||||
def test_template_convert_bare_string(self):
|
def test_template_convert_bare_string(self):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue