diff --git a/CHANGELOG.md b/CHANGELOG.md index 49b7925e54..2eabc6c356 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,7 +86,8 @@ Ansible Changes By Release (https://github.com/ansible/ansible/pull/36721) * Protect from inventory plugins using verify incorrectly https://github.com/ansible/ansible/pull/36591 - +* loop control templating + https://github.com/ansible/ansible/pull/36124 diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py index 4533bf4efd..75e2ee8bcb 100644 --- a/lib/ansible/executor/task_executor.py +++ b/lib/ansible/executor/task_executor.py @@ -27,7 +27,7 @@ from ansible import constants as C from ansible.errors import AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleConnectionFailure, AnsibleActionFail, AnsibleActionSkip from ansible.executor.task_result import TaskResult from ansible.module_utils.six import iteritems, string_types, binary_type -from ansible.module_utils._text import to_text +from ansible.module_utils._text import to_text, to_native from ansible.playbook.conditional import Conditional from ansible.playbook.task import Task from ansible.plugins.connection import ConnectionBase @@ -277,11 +277,14 @@ class TaskExecutor: loop_var = 'item' label = None loop_pause = 0 + templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=self._job_vars) if self._task.loop_control: - # the value may be 'None', so we still need to default it back to 'item' - loop_var = self._task.loop_control.loop_var or 'item' - label = self._task.loop_control.label or ('{{' + loop_var + '}}') - loop_pause = self._task.loop_control.pause or 0 + # FIXME: move this to the object itself to allow post_validate to take care of templating + loop_var = templar.template(self._task.loop_control.loop_var) or 'item' + loop_pause = templar.template(self._task.loop_control.pause) + # the these may be 'None', so we still need to default to something useful + # this is tempalted below after an item is assigned + label = (self._task.loop_control.label or ('{{' + loop_var + '}}')) if loop_var in task_vars: display.warning(u"The loop variable '%s' is already in use. " @@ -295,7 +298,10 @@ class TaskExecutor: # pause between loop iterations if loop_pause and ran_once: - time.sleep(loop_pause) + try: + time.sleep(float(loop_pause)) + except ValueError as e: + raise AnsibleError('Invalid pause value: %s, produced error: %s' % (loop_pause, to_native(e))) else: ran_once = True @@ -323,7 +329,6 @@ class TaskExecutor: res['_ansible_ignore_errors'] = task_fields.get('ignore_errors') if label is not None: - templar = Templar(loader=self._loader, shared_loader_obj=self._shared_loader_obj, variables=self._job_vars) res['_ansible_item_label'] = templar.template(label) self._rslt_q.put( @@ -399,7 +404,7 @@ class TaskExecutor: # * lists can be squashed together # * dicts could squash entries that match in all cases except the # name or pkg field. - except: + except Exception: # Squashing is an optimization. If it fails for any reason, # simply use the unoptimized list of items. diff --git a/lib/ansible/playbook/loop_control.py b/lib/ansible/playbook/loop_control.py index b983ee88ed..266e826016 100644 --- a/lib/ansible/playbook/loop_control.py +++ b/lib/ansible/playbook/loop_control.py @@ -23,6 +23,7 @@ from ansible.playbook.attribute import FieldAttribute from ansible.playbook.base import Base +# FIXME: loopcontrol should not inherit attributes from base, just uses it for load class LoopControl(Base): _loop_var = FieldAttribute(isa='str')