Repository: ansible/ansible

Issue:
include_vars is broken in 2.3.0 and 2.2.2
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
include_vars

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.2.2.0
  config file = /home/sean/.ansible.cfg
  configured module search path = Default w/o overrides
```

and
```
ansible 2.3.0.0
  config file = /home/sean/.ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]
```


##### CONFIGURATION
~/.ansible.cfg:
```
[defaults]
roles_path = /home/sean/workspace/infra/ansible_roles
#pipelining = False

[ssh_connection]
ssh_args = -o ForwardAgent=yes
```


##### OS / ENVIRONMENT
```
env | grep ANSIBLE
ANSIBLE_VAULT_PASSWORD_FILE=/home/sean/.pd_vault_pass.txt
```

##### SUMMARY
include_vars is working with a role in 2.2.1.  It is not working in 2.2.2 or 2.3.0.

##### STEPS TO REPRODUCE


<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: "get dynamic includes"
  include_vars: dynamic_includes.yml
- name: "debug api_jar_filename"
  debug:
    var: api_jar_filename
    verbosity: 1
```

(dynamic_includes.yml)
```yaml
---
# These variables will only work if other variables (timestamp) have been registered
release_time: "{{ timestamp.stdout }}"
release_path: "{{ finmason_api_deploy_path }}/{{ release_time }}"
api_jar_filename: "fmserver-api-{{ finmason_api_version }}-{{ git_info['git_7_hash'] }}.jar"
local_api_jar_path: "/tmp/{{ api_jar_filename }}"
```


##### EXPECTED RESULTS
From run with 2.2.1:
```
TASK [api_capr : get dynamic includes] *****************************************
ok: [10.x.x.x -> 127.0.0.1] => {"ansible_facts": {"api_jar_filename": "fmserver-api-{{ finmason_api_version }}-{{ git_info['git_7_hash'] }}.jar", "local_api_jar_path": "/tmp/{{ api_jar_filename }}", "release_path": "{{ finmason_api_deploy_path }}/{{ release_time }}", "release_time": "{{ timestamp.stdout }}"}, "changed": false}
TASK [api_capr : debug api_jar_filename] ***************************************
ok: [10.x.x.x -> 127.0.0.1] => {
    "api_jar_filename": "fmserver-api-STAT-SNAPSHOT-4e824cc.jar"
}
```

##### ACTUAL RESULTS
From ansible 2.3.0 run.  Results are identical in 2.2.2 run.

```
TASK [api_capr : get dynamic includes] *****************************************************************************************************************************************************************************
task path: /home/sean/workspace/infra/ansible_roles/api_capr/tasks/get_jar.yml:58
ok: [10.x.x.x -> 127.0.0.1] => {
    "ansible_facts": {
        "api_jar_filename": "fmserver-api-{{ finmason_api_version }}-{{ git_info['git_7_hash'] }}.jar", 
        "local_api_jar_path": "/tmp/{{ api_jar_filename }}", 
        "release_path": "{{ finmason_api_deploy_path }}/{{ release_time }}", 
        "release_time": "{{ timestamp.stdout }}"
    }, 
    "changed": false
}
TASK [api_capr : debug api_jar_filename] ***************************************************************************************************************************************************************************
task path: /home/sean/workspace/infra/ansible_roles/api_capr/tasks/get_jar.yml:66
ok: [10.x.x.x -> 127.0.0.1] => {
    "api_jar_filename": "VARIABLE IS NOT DEFINED!", 
    "changed": false
}
```



Discussion:
I am seeing the same thing.  I just upgraded from 2.1, most of the time the include_vars works (I do an include_vars of the same file from many different places).  So far, in one case, I have found this error.  Same kind of debug, include the file, then debug the variable that was set (worklog).  Here is the code that does the include_vars that works most of the time: 
```
    - name: get user
      delegate_to: localhost
      include_vars: "{{home}}/.vault/worklog.yml"
```
The next tasks references worklog, which is set in the yml file: 
``` 
{"worklog": {"email": "testmeila@doink.com", "pass": "12312313-9912-7312-9212-187987197913"}}
``` 
Normally this works, but in this one case it didn't.  I removed the delegate_to: localhost, and now it works for me.  I am sure it is still lurking as this bug doesn't show a delegate_to: localhost.
When you delegate you are applying the task to that host, so the vars will be in `localhost`, not in `inventory_hostname`

Actually, that seems likely/appropriate as that include is happening inside of a 

```
- include: get_jar.yml
  tags:
    - "get_jar"
  run_once: "true"
delegate_to: 127.0.0.1
```
I was having another bug where delegate_facts isn't working.  I wonder if they're related.
Yes, but in my example both the include_vars and the debug statement are both in the "get_jar" playbook above.  The latter immediately follows the former, and they are both delegated to localhost.
delgation does not mean you get all vars, only connection vars, the rest of the vars are from the `inventory_hostname`
...So then was this "broken" in 2.2.1?  Because this is a playbook I've been using daliy for 6 months.  It makes sense that if I set a var in a playbook in it's entirety being run against a particular host, that var would be available for the rest of the same playbook. It doesn't make any sense for it to disappear immediately after it's set.

Resolving patch:
diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py
--- a/lib/ansible/plugins/strategy/__init__.py
+++ b/lib/ansible/plugins/strategy/__init__.py
@@ -490,25 +490,19 @@ def parent_handler_match(target_handler, handler_name):
 
                     if 'ansible_facts' in result_item:
 
-                        if original_task.action == 'include_vars':
-
-                            if original_task.delegate_to is not None:
-                                host_list = self.get_delegated_hosts(result_item, original_task)
-                            else:
-                                host_list = self.get_task_hosts(iterator, original_host, original_task)
+                        # if delegated fact and we are delegating facts, we need to change target host for them
+                        if original_task.delegate_to is not None and original_task.delegate_facts:
+                            host_list = self.get_delegated_hosts(result_item, original_task)
+                        else:
+                            host_list = self.get_task_hosts(iterator, original_host, original_task)
 
+                        if original_task.action == 'include_vars':
                             for (var_name, var_value) in iteritems(result_item['ansible_facts']):
                                 # find the host we're actually referring too here, which may
                                 # be a host that is not really in inventory at all
                                 for target_host in host_list:
                                     self._variable_manager.set_host_variable(target_host, var_name, var_value)
                         else:
-                            # if delegated fact and we are delegating facts, we need to change target host for them
-                            if original_task.delegate_to is not None and original_task.delegate_facts:
-                                host_list = self.get_delegated_hosts(result_item, original_task)
-                            else:
-                                host_list = self.get_task_hosts(iterator, original_host, original_task)
-
                             for target_host in host_list:
                                 if original_task.action == 'set_fact':
                                     self._variable_manager.set_nonpersistent_facts(target_host, result_item['ansible_facts'].copy())


Test patch: