Repository: ansible/ansible

Issue:
Setup does not gather delegated facts 
<!---
Please do not report issues/requests related to Ansible modules here !!

Report them to the appropriate modules-core or modules-extras project:
  - https://github.com/ansible/ansible-modules-core/issues
  - https://github.com/ansible/ansible-modules-extras/issues

Also verify first that your issue/request is not already reported in GitHub
-->
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME

gathering
##### ANSIBLE VERSION

```
ansible 2.2.0 (devel da4c3ebeac) last updated 2016/09/14 10:36:53 (GMT +300)
  lib/ansible/modules/core: (detached HEAD ae6992bf8c) last updated 2016/09/14 10:38:12 (GMT +300)
  lib/ansible/modules/extras: (detached HEAD afd0b23836) last updated 2016/09/14 10:38:24 (GMT +300)
```
##### CONFIGURATION

[defaults]
host_key_checking=False
timeout=30
lookup_plugins=./library/lookup_plugins

[ssh_connection]
ssh_args=-o ForwardAgent=yes
##### OS / ENVIRONMENT

N/A
##### SUMMARY

Running setup with delegate_to and delegate_facts: True to get facts about hosts not in the current play does not yield facts from the delegated to hosts
##### STEPS TO REPRODUCE

Tasks:

```
- name: gather inventory facts
  setup:
  delegate_to: "{{ item }}"
  delegate_facts: yes
  with_items: "{{ groups['all'] }}"
  when: "item not in play_hosts"

- debug:
    msg: "{{ hostvars[item]['ansible_eth0'] }}"
  with_items: "{{ groups['all'] }}"
  run_once: yes

```

Run like:

```
$ ansible-playbook playbook.yml --limit somehosts
```
##### EXPECTED RESULTS

```
< TASK [debug] >
 ------------------------
ok: [host01] => (item=other02) => {
    "item": "other02", 
    "msg": {
        "active": true, 
        "device": "eth0", 
        "features": {
            [...]
        }, 
        "ipv4": {
            "address": "X.X.X.X", 
            [...]
        }, 
        [...]
        "type": "ether"
    }
}
```
##### ACTUAL RESULTS

```
 ________________________
< TASK [debug] >
 ------------------------

fatal: [host01]: FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'dict object' has no attribute 'ansible_eth0'\n\nThe error appears to have been in '/[...]/tasks/main.yml': line 46, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- debug:\n  ^ here\n"}
```



Discussion:
@Jorge-Rodriguez: Thanks for taking the time to open this issue, but it was filed under the wrong github repository. Please create a new issue at https://github.com/ansible/ansible-modules-core/issues
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_wrong_repo --->

Note that this was solved on ansible 2.1.0.0 with a change on `lib/ansible/playbook/play_context.py`

cc @bcoca 

@Jorge-Rodriguez I believe this may be a misunderstanding. In your debug loop, you're showing facts for all systems however you've filtered out some in the explicit `setup` task (namely those that are specified in your `--limit`). By setting `gather_facts: yes` on the play, I see this work as expected:

<pre>
[jamesc@jimi 17582]$ cat test.yml
- hosts: all
  gather_facts: yes
  remote_user: root
  tasks:
  - name: gather inventory facts
    setup:
    delegate_to: "{{ item }}"
    delegate_facts: yes
    with_items: "{{ groups['all'] }}"
    when: "item not in play_hosts"
  - debug:
      msg: "{{ hostvars[item]['ansible_eth0'] }}"
    with_items: "{{ groups['all'] }}"
    run_once: yes
[jamesc@jimi 17582]$  cat hosts
awxlocal ansible_ssh_host=192.168.122.100
ubuntu1204 ansible_ssh_host=192.168.122.110
ubuntu1310 ansible_ssh_host=192.168.122.112
ubuntu1404 ansible_ssh_host=192.168.122.113
[jamesc@jimi 17582]$ ansible-playbook -vv -i hosts test.yml --limit=awxlocal
Using /etc/ansible/ansible.cfg as config file
PLAYBOOK: test.yml *************************************************************
1 plays in test.yml
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
delegate_to: None, delegate_facts: False
ok: [awxlocal]
TASK [gather inventory facts] **************************************************
task path: /home/jamesc/testing/17582/test.yml:5
skipping: [awxlocal] => (item=awxlocal)  => {"changed": false, "item": "awxlocal", "skip_reason": "Conditional check failed", "skipped": true}
ok: [awxlocal -> None] => (item=ubuntu1204)
ok: [awxlocal -> None] => (item=ubuntu1310)
ok: [awxlocal -> None] => (item=ubuntu1404)
delegate_to: {{ item }}, delegate_facts: True
^ ok, delegating facts
^ target host = ubuntu1204
^ actual host is: ubuntu1204
delegate_to: {{ item }}, delegate_facts: True
^ ok, delegating facts
^ target host = ubuntu1310
^ actual host is: ubuntu1310
delegate_to: {{ item }}, delegate_facts: True
^ ok, delegating facts
^ target host = ubuntu1404
^ actual host is: ubuntu1404
TASK [debug] *******************************************************************
task path: /home/jamesc/testing/17582/test.yml:12
ok: [awxlocal] => (item=awxlocal) => {
    "item": "awxlocal", 
    "msg": {
        "active": true, 
        "device": "eth0", 
        "ipv4": {
            "address": "192.168.122.100", 
            "broadcast": "192.168.122.255", 
            "netmask": "255.255.255.0", 
            "network": "192.168.122.0"
        }, 
        "ipv6": [
            {
                "address": "fe80::216:3eff:fe1c:3ec9", 
                "prefix": "64", 
                "scope": "link"
            }
        ], 
        "macaddress": "00:16:3e:1c:3e:c9", 
        "module": "virtio_net", 
        "mtu": 1500, 
        "pciid": "virtio0", 
        "promisc": false, 
        "type": "ether"
    }
}
ok: [awxlocal] => (item=ubuntu1204) => {
    "item": "ubuntu1204", 
    "msg": {
        "active": true, 
        "device": "eth0", 
        "ipv4": {
            "address": "192.168.122.110", 
            "broadcast": "192.168.122.255", 
            "netmask": "255.255.255.0", 
            "network": "192.168.122.0"
        }, 
        "ipv6": [
            {
                "address": "fe80::216:3eff:fe77:59c8", 
                "prefix": "64", 
                "scope": "link"
            }
        ], 
        "macaddress": "00:16:3e:77:59:c8", 
        "mtu": 1500, 
        "pciid": "virtio0", 
        "promisc": false, 
        "type": "ether"
    }
}
ok: [awxlocal] => (item=ubuntu1310) => {
    "item": "ubuntu1310", 
    "msg": {
        "active": true, 
        "device": "eth0", 
        "ipv4": {
            "address": "192.168.122.112", 
            "broadcast": "192.168.122.255", 
            "netmask": "255.255.255.0", 
            "network": "192.168.122.0"
        }, 
        "ipv6": [
            {
                "address": "fe80::216:3eff:fe16:48ae", 
                "prefix": "64", 
                "scope": "link"
            }
        ], 
        "macaddress": "00:16:3e:16:48:ae", 
        "mtu": 1500, 
        "pciid": "virtio0", 
        "promisc": false, 
        "type": "ether"
    }
}
ok: [awxlocal] => (item=ubuntu1404) => {
    "item": "ubuntu1404", 
    "msg": {
        "active": true, 
        "device": "eth0", 
        "ipv4": {
            "address": "192.168.122.113", 
            "broadcast": "192.168.122.255", 
            "netmask": "255.255.255.0", 
            "network": "192.168.122.0"
        }, 
        "ipv6": [
            {
                "address": "fe80::5054:ff:fe60:3f3e", 
                "prefix": "64", 
                "scope": "link"
            }
        ], 
        "macaddress": "52:54:00:60:3f:3e", 
        "mtu": 1500, 
        "pciid": "virtio0", 
        "promisc": false, 
        "type": "ether"
    }
}
PLAY RECAP *********************************************************************
awxlocal                   : ok=3    changed=0    unreachable=0    failed=0   
</pre>

If you're not gathering facts on the things you're filtering out of your explicit setup task, you see your reported result.

confirmed the issue (locals group has localhost, local1,local2,local3):

```
- hosts: localhost
  gather_facts: yes
  tasks:
    - name: gather inventory facts
      setup:
      delegate_to: "{{ item }}"
      delegate_facts: yes
      with_items: "{{ groups['locals'] }}"
      when: "item not in play_hosts"

    - debug:
        msg: "{{ hostvars[item]['ansible_eth0'] }}"
      with_items: "{{ groups['locals'] }}"
      run_once: yes
```

if you change to `msg: "{{ hostvars[item]}}"` you avoid the error and you can see only localhost has facts, the rest only have other vars.

checked against 2.1 and it does not have the issue

I think i found the culprit, but many changes here https://github.com/ansible/ansible/commit/d2b3b2c03e2934b126f5701e5f6e25821e2dbe35


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
@@ -19,7 +19,6 @@
 from __future__ import (absolute_import, division, print_function)
 __metaclass__ = type
 
-import os
 import threading
 import time
 
@@ -35,7 +34,6 @@
 from ansible.executor.task_result import TaskResult
 from ansible.inventory.host import Host
 from ansible.inventory.group import Group
-from ansible.module_utils.facts import Facts
 from ansible.playbook.helpers import load_list_of_blocks
 from ansible.playbook.included_file import IncludedFile
 from ansible.playbook.task_include import TaskInclude
@@ -438,28 +436,33 @@ def parent_handler_match(target_handler, handler_name):
                         self._add_group(original_host, result_item)
 
                     elif 'ansible_facts' in result_item:
-                        loop_var = 'item'
+
+                        # set correct loop var
                         if original_task.loop_control:
                             loop_var = original_task.loop_control.loop_var or 'item'
+                        else:
+                            loop_var = 'item'
 
                         item = result_item.get(loop_var, None)
 
+                        # 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:
+                            task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=original_host, task=original_task)
+                            self.add_tqm_variables(task_vars, play=iterator._play)
+                            if item is not None:
+                                task_vars[loop_var] = item
+                            templar = Templar(loader=self._loader, variables=task_vars)
+                            host_name = templar.template(original_task.delegate_to)
+                            actual_host = self._inventory.get_host(host_name)
+                            if actual_host is None:
+                                actual_host = Host(name=host_name)
+                        else:
+                            actual_host = original_host
+
                         if original_task.action == 'include_vars':
                             for (var_name, var_value) in iteritems(result_item['ansible_facts']):
                                 # find the host we're actually refering too here, which may
                                 # be a host that is not really in inventory at all
-                                if original_task.delegate_to is not None and original_task.delegate_facts:
-                                    task_vars = self._variable_manager.get_vars(loader=self._loader, play=iterator._play, host=host, task=original_task)
-                                    self.add_tqm_variables(task_vars, play=iterator._play)
-                                    if item is not None:
-                                        task_vars[loop_var] = item
-                                    templar = Templar(loader=self._loader, variables=task_vars)
-                                    host_name = templar.template(original_task.delegate_to)
-                                    actual_host = self._inventory.get_host(host_name)
-                                    if actual_host is None:
-                                        actual_host = Host(name=host_name)
-                                else:
-                                    actual_host = original_host
 
                                 if original_task.run_once:
                                     host_list = [host for host in self._inventory.get_hosts(iterator._play.hosts) if host.name not in self._tqm._unreachable_hosts]
@@ -472,7 +475,7 @@ def parent_handler_match(target_handler, handler_name):
                             if original_task.run_once:
                                 host_list = [host for host in self._inventory.get_hosts(iterator._play.hosts) if host.name not in self._tqm._unreachable_hosts]
                             else:
-                                host_list = [original_host]
+                                host_list = [actual_host]
 
                             for target_host in host_list:
                                 if original_task.action == 'set_fact':


Test patch: