Repository: ansible/ansible

Issue:
loop_control/label shows wrong value when referencing task-level var
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
loop_control/label

##### ANSIBLE VERSION
```
ansible 2.5.0
```

##### CONFIGURATION
```
ANSIBLE_PIPELINING(/etc/ansible/ansible.cfg) = True
DEFAULT_SCP_IF_SSH(/etc/ansible/ansible.cfg) = True
RETRY_FILES_ENABLED(/etc/ansible/ansible.cfg) = False
```

##### OS / ENVIRONMENT
N/A

##### SUMMARY
When using the `loop_control`/`label` feature and referencing a task-level var, the var seems to be evaluated only once.

##### STEPS TO REPRODUCE
```yaml
- hosts: localhost
  gather_facts: no
  tasks:
    - name: test stuff
      vars:
        foo: '{{ item|int }}'
      debug:
        var: foo
      with_sequence: start=1 count=3
      loop_control:
        label: '{{ foo }}'
```

##### EXPECTED RESULTS
```
PLAY [localhost] **************************************************************************************************************************************************************************************************************************************************************

TASK [test stuff] *************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=1) => {
    "foo": "1"
}
ok: [localhost] => (item=2) => {
    "foo": "2"
}
ok: [localhost] => (item=3) => {
    "foo": "3"
}

PLAY RECAP ********************************************************************************************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0   
```

##### ACTUAL RESULTS
```
PLAY [localhost] **************************************************************************************************************************************************************************************************************************************************************

TASK [test stuff] *************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=1) => {
    "foo": "1"
}
ok: [localhost] => (item=1) => {
    "foo": "2"
}
ok: [localhost] => (item=1) => {
    "foo": "3"
}

PLAY RECAP ********************************************************************************************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0  
```



Discussion:
Files identified in the description:
None

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
I encountered this same issue in 2.4.4 after upgrading from 2.4.2 due to a memory issue (2.4.2 displayed the labels correctly).

The playbook I found this in was a lot more complicated, but by experimenting I was able to satisfy myself that only the label was displayed incorrectly.  The loop variable items still seem to be correct in the underlying data, and actions using that data appear correct.
**Importantly:** the loop correctly reports both "changed" and "failed" status for each iteration.

I tested the example above with addition of a register, so that the internal loop data can be seen. From this it is clear that "_ansible_item_label" _should_ be changed but isn't. (However, I'm unsure about the other "_ansible.." loop variables - in this simple example, the values are the same for each item.)

Hopefully this helps:

**Modified playbook**
```yml
- hosts: localhost
  gather_facts: no

  tasks:
    - name: test stuff
      vars:
        foo: '{{ item|int }}'
      debug:
        var: foo
      with_sequence: start=1 count=3
      loop_control:
        label: '{{ foo }}'
      register: baz

    - name: show stuff
      debug:
        var: baz
```

**Results**
```
PLAY [localhost] *****************************************************************

TASK [test stuff] ****************************************************************
ok: [localhost] => (item=1) => {
    "foo": "1"
}
ok: [localhost] => (item=1) => {
    "foo": "2"
}
ok: [localhost] => (item=1) => {
    "foo": "3"
}

TASK [show stuff] ****************************************************************
ok: [localhost] => {
    "baz": {
        "changed": false,
        "msg": "All items completed",
        "results": [
            {
                "_ansible_ignore_errors": null,
                "_ansible_item_label": "1",
                "_ansible_item_result": true,
                "_ansible_no_log": false,
                "_ansible_verbose_always": true,
                "changed": false,
                "failed": false,
                "foo": "1",
                "item": "1"
            },
            {
                "_ansible_ignore_errors": null,
                "_ansible_item_label": "1",
                "_ansible_item_result": true,
                "_ansible_no_log": false,
                "_ansible_verbose_always": true,
                "changed": false,
                "failed": false,
                "foo": "2",
                "item": "2"
            },
            {
                "_ansible_ignore_errors": null,
                "_ansible_item_label": "1",
                "_ansible_item_result": true,
                "_ansible_no_log": false,
                "_ansible_verbose_always": true,
                "changed": false,
                "failed": false,
                "foo": "3",
                "item": "3"
            }
        ]
    }
}

PLAY RECAP ***********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
```

I'm on 2.4.4 as well, and see this when using `with_subelements`.

**Playbook**
```
- hosts: localhost
  gather_facts: False
  vars:
    myvar:
      - x: "first x value"
        y:
          - "y1": "first y1 value"
            "y2": "first y2 value"
      - x: "second x value"
        y:
          - "y1": "second y1 value"
            "y2": "second y2 value"
  tasks:
    - name: the with_subelements way
      debug:
        msg: "{{ item.1.y1 }}"
      with_subelements:
        - "{{ myvar }}"
        - "y"
      loop_control:
        label: "{{ item.0.x }}"

    - name: the lookup way
      debug:
        msg: "{{ item.1.y1 }}"
      with_items:
        - "{{ lookup('subelements', myvar, 'y') }}"
      loop_control:
        label: "{{ item.0.x }}"
```
**Actual Results**
```
PLAY [localhost] ***************************************************************

TASK [the with_subelements way] ************************************************
ok: [localhost] => (item=first x value) => {
    "msg": "first y1 value"
}
ok: [localhost] => (item=first x value) => {
    "msg": "second y1 value"
}

TASK [the lookup way] **********************************************************
ok: [localhost] => (item=first x value) => {
    "msg": "first y1 value"
}
ok: [localhost] => (item=first x value) => {
    "msg": "second y1 value"
}

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
```
**Expected Results**
```
PLAY [localhost] ***************************************************************

TASK [the with_subelements way] ************************************************
ok: [localhost] => (item=first x value) => {
    "msg": "first y1 value"
}
ok: [localhost] => (item=second x value) => {
    "msg": "second y1 value"
}

TASK [the lookup way] **********************************************************
ok: [localhost] => (item=first x value) => {
    "msg": "first y1 value"
}
ok: [localhost] => (item=second x value) => {
    "msg": "second y1 value"
}

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
```
@bcoca do you know what changed that is causing this? You indicated on IRC that it probably had something to do with task-level vars caching.
A git bisect indicates that this was caused be 4546e7d1d7262485c21cfb1ce5ae2ab577a9bcf2
It seems to be the side effect of moving `templar = Templar(...)` out of the for loop. This indicates that some set of vars being used in `Templar` are not being mutated, but instead overwritten.
That seems to fit with what bcoca had said on IRC. The previous code re-instantiated the Templar class on each loop iteration, but now it only happens once for the task.
This could *probably* be fixed by just bringing back the line to re-instantiate the `Templar` class for each loop iteration. This would result in `n+1` instantiations of Templar for a task that loops `n` times, vs. the previous `n` instantiations from before this fix, but would satisfy the intentions of bcoca's change.

Resolving patch:
diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py
--- a/lib/ansible/executor/task_executor.py
+++ b/lib/ansible/executor/task_executor.py
@@ -302,6 +302,9 @@ def _run_loop(self, items):
             if index_var:
                 task_vars[index_var] = item_index
 
+            # Update template vars to reflect current loop iteration
+            templar.set_available_variables(task_vars)
+
             # pause between loop iterations
             if loop_pause and ran_once:
                 try:


Test patch: