Repository: ansible/ansible
Pull request: fix block var inheritance
fixes #75286

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
- Bugfix Pull Request

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below -->
core

Problem statement:
`vars:` keyword on inner `block:` does not override `vars:` in outer `block:`
### Summary

If a var is defined in the `vars:` of a block, and then redefined again in the `vars:` of an inner block, the outer value is still used.

`vars:` on a _task_ in the inner block work as expected.

### Issue Type

Bug Report

### Component Name

unknown

### Ansible Version

```console
$ ansible --version
ansible [core 2.12.0.dev0] (devel 1fc1ab89ae) last updated 2021/07/17 12:29:56 (GMT -400)
  config file = None
  configured module search path = ['/home/briantist/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/briantist/code/ansible/ansible.core/lib/ansible
  ansible collection location = /home/briantist/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/briantist/code/ansible/ansible.core/bin/ansible
  python version = 3.6.9 (default, Jan 26 2021, 15:33:00) [GCC 8.4.0]
  jinja version = 3.0.1
  libyaml = True
```


### Configuration

```console
$ ansible-config dump --only-changed
CALLBACKS_ENABLED(env: ANSIBLE_CALLBACK_WHITELIST) = ['profile_tasks']
DEFAULT_FORKS(env: ANSIBLE_FORKS) = 30
INVENTORY_ENABLED(env: ANSIBLE_INVENTORY_ENABLED) = ['host_list', 'auto', 'ini', 'yaml', 'toml']
```


### OS / Environment

Ubuntu 18.04 on WSL2

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: outer
  vars:
    myvar: abc
  block:
    - debug:
        var: myvar

    - name: inner block
      vars:
        myvar: 123
      block:

        - debug:
            var: myvar

        - name: task
          vars:
            myvar: xyz
          debug:
            var: myvar
```

### Expected Results

inner block first `debug` result is `123`

### Actual Results

inner block first `debug` result is `abc`


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct

Hints from issue discussion:
Files identified in the description:
None

If these files are incorrect, 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 --->

Commit message: Update changelogs/fragments/fix_block_var_inh.yml

Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com>

Patch to review:
diff --git a/changelogs/fragments/fix_block_var_inh.yml b/changelogs/fragments/fix_block_var_inh.yml
new file mode 100644
index 00000000000000..770e54962332f2
--- /dev/null
+++ b/changelogs/fragments/fix_block_var_inh.yml
@@ -0,0 +1,2 @@
+bugfixes:
+  - correctly inherit vars from parent in block.
diff --git a/lib/ansible/playbook/block.py b/lib/ansible/playbook/block.py
index 3c8ecc76291c7e..a9b399f2f9fb02 100644
--- a/lib/ansible/playbook/block.py
+++ b/lib/ansible/playbook/block.py
@@ -79,11 +79,13 @@ def get_vars(self):
         of a role or task include which does, so return those if present.
         '''
 
-        all_vars = self.vars.copy()
+        all_vars = {}
 
         if self._parent:
             all_vars.update(self._parent.get_vars())
 
+        all_vars.update(self.vars.copy())
+
         return all_vars
 
     @staticmethod
diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role/tasks/main.yml
index d41ae90e95d300..3fab7fe977aac6 100644
--- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role/tasks/main.yml
+++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role/tasks/main.yml
@@ -26,4 +26,4 @@
 
   - assert:
       that:
-        - test_role_output.msg == test_role_input
+        - non_coll_role_to_call_test_role_output.msg == test_role_input
diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role_to_call/tasks/main.yml b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role_to_call/tasks/main.yml
index 98445ce3aafbc3..ac5866b00e8292 100644
--- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role_to_call/tasks/main.yml
+++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role_to_call/tasks/main.yml
@@ -1,7 +1,7 @@
 - debug:
     msg: '{{ test_role_input | default("(undefined)") }}'
-  register: test_role_output
+  register: non_coll_role_to_call_test_role_output
 
 - assert:
     that:
-    - test_role_input is not defined or test_role_input == test_role_output.msg
+    - non_coll_role_to_call_test_role_output is not defined or non_coll_role_to_call_test_role_output == test_role_output.msg
diff --git a/test/integration/targets/var_inheritance/aliases b/test/integration/targets/var_inheritance/aliases
new file mode 100644
index 00000000000000..3005e4b26d029a
--- /dev/null
+++ b/test/integration/targets/var_inheritance/aliases
@@ -0,0 +1 @@
+shippable/posix/group4
diff --git a/test/integration/targets/var_inheritance/tasks/main.yml b/test/integration/targets/var_inheritance/tasks/main.yml
new file mode 100644
index 00000000000000..48d7b3d3a8cb93
--- /dev/null
+++ b/test/integration/targets/var_inheritance/tasks/main.yml
@@ -0,0 +1,16 @@
+- name: outer
+  vars:
+    myvar: abc
+  block:
+    - assert:
+        that:
+          - "myvar == 'abc'"
+
+    - name: inner block
+      vars:
+        myvar: 123
+      block:
+
+      - assert:
+          that:
+            - myvar|int == 123


Code review comments:
diff_hunk: @@ -1,7 +1,7 @@
 - debug:
     msg: '{{ test_role_input | default("(undefined)") }}'
-  register: test_role_output
+  register: non_coll_role_to_call_test_role_output
 
 - assert:
     that:
-    - test_role_input is not defined or test_role_input == test_role_output.msg
+    - non_coll_role_to_call_test_role_output is not defined or non_coll_role_to_call_test_role_output == test_role_output.msg
path: test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/playbooks/roles/non_coll_role_to_call/tasks/main.yml
text: @user1:
test_role_output.msg is either "task static legacy embedded default collection" or "task dynamic default collection" at this point, and test_role_input should inherit from the parent block now (so it's "include another non-coll role") which is why tests are failing. It seems like it was testing that the inheritance wasn't happening.

I think the assertion should now be
```suggestion
    - 'test_role_input is defined and test_role_input == "include another non-coll role"'
```
Or maybe just
```suggestion
    - 'non_coll_role_to_call_test_role_output.msg == "include another non-coll role"'
```
Or maybe we should test that test_role_output.msg != non_coll_role_to_call_test_role_output.msg since they should never be the same now.