Accessing other hosts facts works in debug task, but not anywhere else

Hello, I have a group of hosts that have to access to each others IP address. I have the following code:

- debug:
    var: hostvars[item]['ansible_default_ipv4']['address']
  loop: "{{ groups[cluster_name] | flatten(levels=1) }}"

- name: Register private IP addresses of the cluster
  set_fact:
    cluster_ips: "{{ cluster_ips | default({}) | combine({item: hostvars[item]['ansible_default_ipv4']['address']}) }}"
  loop: "{{ groups[cluster_name] | flatten(levels=1) }}"

You can see that I access the same facts in debug and in set_fact. The first task works perfectly but the second one fails.

fatal: [host1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'ansible_default_ipv4'. 'ansible.vars.hostvars.HostVarsVars object' has no attribute 'ansible_default_ipv4'\n\nThe error appears to be in '/home/raspbeguy/repo/infra/ansible/roles/myrole/tasks/main.yml': line 10, 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- name: Register private IP addresses of the cluster\n  ^ here\n"}

before trying with set_fact I was trying to use it in a template, didn’t work either.

I’m using the last version of ansible available on pip:

ansible [core 2.16.7]
  config file = /home/raspbeguy/repo/infra/ansible/ansible.cfg
  configured module search path = ['/home/raspbeguy/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/raspbeguy/repo/infra/ansible/venv/lib/python3.10/site-packages/ansible
  ansible collection location = /home/raspbeguy/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/raspbeguy/repo/infra/ansible/venv/bin/ansible
  python version = 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] (/home/raspbeguy/repo/infra/ansible/venv/bin/python3)
  jinja version = 3.1.4
  libyaml = True

I don’t understand why this is happening.

You need to gather_facts in a loop for the all the hosts first?

Hello @chris,

What do you mean by running it in a loop? gather_facts doesn’t take any host argument (I guess it gather facts from all hosts) so I don’t see how and why running it in a loop. Either way, ansible gather facts already at the playbook start, and even after I added this task manually it won’t work either.

Sorry if I misunderstood the issue, I saw this error:

The error was: ‘ansible.vars.hostvars.HostVarsVars object’ has no attribute ‘ansible_default_ipv4’.

And assumed it was a result of facts not having being gathered for a host, but perhaps that isn’t the case here?

If that is the case I was thinking of something like this to ensure that the facts are gathered for all the hosts:

- name: Gather facts for all hosts
  ansible.builtin.gather_facts:
  delegate_to: "{{ host }}"
  loop: "{{ hosts }}"
  loop_control:
    loop_var: host
1 Like

you also want delegate_facts: true so each host gets the facts for itself instead of continually overwriting the facts for the inventory_hostname

2 Likes

@bcoca @chris that works, thanks ! I had to add delegate_facts: true for it to work.

However, it means that each host has to initiate fact gathering about every other hosts targeted by the playbook. This makes a complexity of n^2, which is kind of problematic…

for instance let’s say my inventory is like this:

[cluster_a]
host-a-1
host-a-2
host-a-3

[cluster_a:vars]
cluster_name=cluster_a

[cluster_b]
host-b-1
host-b-2
host-b-3

[cluster_b:vars]
cluster_name=cluster_b

The group var cluster_name is here to let the hosts know what ansible group represent their cluster.

Is there a way to limit fact gathering for each host to the hosts that are in the same cluster? In the example I wish that host-a-1 gathers facts only for the 3 hosts in its cluster, and not the 6 total hosts when I run the playbook without limit.

Can you loop on the cluster_name? For example:

- name: Gather facts for all hosts
  ansible.builtin.gather_facts:
  delegate_to: "{{ host }}"
  delegate_facts: true
  loop: "{{ groups['cluster_name'] }}"
  loop_control:
    loop_var: host

The example in the documentation appears to have the answer.

This is already what I have.
Actually I’m not sure, because for now I have only one cluster, but I think the loop though groups[cluster_name] will just tell each hosts in group cluster_name to gather facts about all hosts in the inventory, it won’t limit the taget of the gathering.

Also I have no idea what will happen when there will be more than one cluster, so with multiple values of cluster_name. Will there be as many loops as there are cluster_name values?

I’ll setup a second test cluster next week to find out.

simpler, create a 2nd play that runs first that just gathers facts on those hosts

You can also run_once: true to avoid running the task per host, if you cannot use a 2nd play