Repository: ansible/ansible

Issue:
conditional include_tasks passes conditions down to included tasks
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
core

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible-playbook 2.4.0 (devel ca7ce4459d) last updated 2017/08/07 19:40:07 (GMT +550)
  config file = None
  configured module search path = [u'/home/ams/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/ams/build/ansible/lib/ansible
  executable location = /home/ams/build/ansible/bin/ansible-playbook
  python version = 2.7.9 (default, Jun 29 2016, 13:08:31) [GCC 4.9.2]
```

##### CONFIGURATION
N/A

##### OS / ENVIRONMENT
N/A

##### SUMMARY
Ansible does not behave according to the documentation for ``include_tasks``:

```
* For dynamic includes, the task options will *only* apply to the dynamic task as it is evaluated, and will not be copied to child tasks.
```

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->
First file, ``a.yml``:
<!--- Paste example playbooks or commands between quotes below -->
```yaml
- hosts: localhost
  gather_facts: no
  tasks:
    - include_tasks: x.yml
      when:
        x is not defined
```
Second file, ``x.yml``:
```yaml
- set_fact:
    x: 42
- debug: var=x
```

Then run:
```
ansible-playbook a.yml
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
I expected the value of x to be printed by the second included ``debug`` task.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
PLAY [localhost] ******************************************************************************************************************************************************************************

TASK [include_tasks] **************************************************************************************************************************************************************************
included: /home/ams/src/ansible-tests/x.yml for localhost

TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]

TASK [debug] **********************************************************************************************************************************************************************************
skipping: [localhost]

PLAY RECAP ************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0   
```
cc: @jimi-c 


Discussion:
Marking this as a P2 so I don't forget to clean up these kinds of uses.

Resolving patch:
diff --git a/lib/ansible/playbook/block.py b/lib/ansible/playbook/block.py
--- a/lib/ansible/playbook/block.py
+++ b/lib/ansible/playbook/block.py
@@ -294,11 +294,12 @@ def _get_parent_attribute(self, attr, extend=False, prepend=False):
 
             if self._parent and (value is None or extend):
                 try:
-                    parent_value = getattr(self._parent, attr, None)
-                    if extend:
-                        value = self._extend_value(value, parent_value, prepend)
-                    else:
-                        value = parent_value
+                    if attr != 'when' or getattr(self._parent, 'statically_loaded', True):
+                        parent_value = getattr(self._parent, attr, None)
+                        if extend:
+                            value = self._extend_value(value, parent_value, prepend)
+                        else:
+                            value = parent_value
                 except AttributeError:
                     pass
             if self._role and (value is None or extend):
@@ -386,3 +387,11 @@ def all_parents_static(self):
             return self._parent.all_parents_static()
 
         return True
+
+    def get_first_parent_include(self):
+        from ansible.playbook.task_include import TaskInclude
+        if self._parent:
+            if isinstance(self._parent, TaskInclude):
+                return self._parent
+            return self._parent.get_first_parent_include()
+        return None
diff --git a/lib/ansible/playbook/helpers.py b/lib/ansible/playbook/helpers.py
--- a/lib/ansible/playbook/helpers.py
+++ b/lib/ansible/playbook/helpers.py
@@ -279,6 +279,7 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
                     else:
                         task_list.extend(included_blocks)
                 else:
+                    t.is_static = False
                     task_list.append(t)
 
             elif 'include_role' in task_ds or 'import_role' in task_ds:
@@ -319,6 +320,9 @@ def load_list_of_tasks(ds, play, block=None, role=None, task_include=None, use_h
                     display.debug('Determined that if include_role static is %s' % str(is_static))
 
                 if is_static:
+                    # we set a flag to indicate this include was static
+                    ir.statically_loaded = True
+
                     # uses compiled list from object
                     blocks, _ = ir.get_block_list(variable_manager=variable_manager, loader=loader)
                     t = task_list.extend(blocks)
diff --git a/lib/ansible/playbook/task.py b/lib/ansible/playbook/task.py
--- a/lib/ansible/playbook/task.py
+++ b/lib/ansible/playbook/task.py
@@ -411,11 +411,12 @@ def _get_parent_attribute(self, attr, extend=False, prepend=False):
         try:
             value = self._attributes[attr]
             if self._parent and (value is None or extend):
-                parent_value = getattr(self._parent, attr, None)
-                if extend:
-                    value = self._extend_value(value, parent_value, prepend)
-                else:
-                    value = parent_value
+                if attr != 'when' or getattr(self._parent, 'statically_loaded', True):
+                    parent_value = getattr(self._parent, attr, None)
+                    if extend:
+                        value = self._extend_value(value, parent_value, prepend)
+                    else:
+                        value = parent_value
         except KeyError:
             pass
 
@@ -456,3 +457,11 @@ def all_parents_static(self):
         if self._parent:
             return self._parent.all_parents_static()
         return True
+
+    def get_first_parent_include(self):
+        from ansible.playbook.task_include import TaskInclude
+        if self._parent:
+            if isinstance(self._parent, TaskInclude):
+                return self._parent
+            return self._parent.get_first_parent_include()
+        return None


Test patch: