-1

I have the following playbook to get the first reachable host.

---
- hosts: all
  gather_facts: yes
  ignore_unreachable: yes

  tasks:

  - name: Get the first good host in the group
    vars:
      query: "[?ansible_facts!=''].inventory_hostname"
    set_fact:
      first_good_host: "{{ ansible_play_hosts | map('extract', hostvars) | list | json_query(query) | first }}"
    delegate_to: localhost  
    run_once: yes

  - name: Aggregate the first good host to reachable group
    ansible.builtin.add_host:
      name: '{{ first_good_host }}'
      groups: 'reachable'
    run_once: yes

  - name: print reachable hosts
    debug:
      msg: "{{ groups['reachable'] }}"

But this playbooks exists with 4 if there is any unreachable host(s). If I pass ignore_unreachable: true, then it exits with 0 but reachable also contains the unreachable host(s), which is what I don't want to.

I have also tried with any_errors_fatal and max_failure_percentage but no luck. I don't want to abort/fail on any unreachable host.

How can I deal with exit code 4 without using ignore_unreachable: true?

Playbook ref: Skip other hosts if first host is reachable in Ansible

U880D
  • 8,601
  • 6
  • 24
  • 40
Dentrax
  • 574
  • 8
  • 22
  • "_But this playbooks exists with `4` if there is any unreachable host(s). If i pass `ignore_unreachable: true`, then it exits with `0` but `reachable` also contains the unreachable host(s), which is what I don't want to._", can explain in more detail the why? And what do try to achieve? – U880D Aug 16 '23 at 09:12

2 Answers2

2

ansible_play_hosts variable already contains only the list of valid reachable hosts of your play. Failed/Unreachable hosts are excluded from this list. Thus, you don't need to create a special custom variable to list only reachable hosts. If you need a list with all hosts including unreachable, you can use ansible_play_hosts_all. It contains the list of all the hosts that were targeted by the play.

  • Good point about [Special variables](https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html#term-ansible_play_hosts) ... – U880D Aug 16 '23 at 09:23
  • Thanks, a great TIL for me! I have three hosts group in my `inventory.ini`, which are `control_planes`, `workers` and `etcds`. I used `all` as a placeholder in this example. How can I select `ansible_play_hosts` just for `control_planes` host group? – Dentrax Aug 16 '23 at 10:09
  • The other this is that, I want to get ONLY ONE REACHABLE instead of all actually. – Dentrax Aug 16 '23 at 10:11
  • To get a single host from the list of reachable you can use simple filters like {{ ansible_play_hosts | first }} or {{ ansible_play_hosts | random }}. – Timur Gadiev Aug 16 '23 at 10:27
  • Thanks but this it still exists with 4 If I pass `ignore_unreachable: true`. Also includes unreachable ones. I wonder how @U880D able to made it exit with 0. – Dentrax Aug 16 '23 at 10:34
  • @Dentrax, by setting `ignore_unreachable: true`. That will result into a Return Code of 0 even if there were unreachable hosts. – U880D Aug 16 '23 at 10:37
  • Ah sorry, yes. It exists with 0 but also includes unreachable hosts if i try it with `ansible.builtin.add_host`. – Dentrax Aug 16 '23 at 10:48
  • @Dentrax, "_... but also includes unreachable hosts if I try it with `ansible.builtin.add_host`_" the minimal reproducible example in [the other answer here](https://stackoverflow.com/a/76912124/6771046) doesn't contain unreachable hosts according the sample output. – U880D Aug 16 '23 at 11:05
0

If I pass ignore_unreachable: true, then it exits with 0 but reachable also contains the unreachable host(s), which is what I don't want to.

With a minimal example playbook

---
- hosts: all
  gather_facts: true
  ignore_unreachable: true

  tasks:

  - name: Get the first good host in the group
    set_fact:
      first_good_host: "{{ ansible_play_hosts | map('extract', hostvars) | list | json_query(query) | first }}"
    vars:
      query: "[?ansible_facts!=''].inventory_hostname"
    delegate_to: localhost
    run_once: yes

  - name: Aggregate the first good host to reachable group
    ansible.builtin.add_host:
      name: '{{ first_good_host }}'
      groups: 'reachable'

- hosts: "{{ groups['reachable'] }}"

  tasks:

  - name: Print reachable hosts
    debug:
      msg: "{{ ansible_play_hosts }}"

I am not able reproduce that

... also contains the unreachable host(s) ...

The dynamic group reachable will only contain the first reachable host resulting into an output of

PLAY [all] *****************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************
ok: [test2.example.com]
ok: [test1.example.com]
fatal: [test3.example.com]: UNREACHABLE! => changed=false
  msg: 'Failed to connect to the host via ssh: ssh: connect to host test3.example.com port 22: No route to host'
  skip_reason: Host test3.example.com is unreachable
  unreachable: true

TASK [Get the first good host in the group] ********************************************************************
ok: [test1.example.com -> localhost]

TASK [Aggregate the first good host to reachable group] ********************************************************
changed: [test1.example.com]

PLAY [[u'test1.example.com']] **********************************************************************************

TASK [Gathering Facts] *****************************************************************************************
ok: [test1.example.com]

TASK [Print reachable hosts] ***********************************************************************************
ok: [test1.example.com] =>
  msg:
  - test1.example.com

PLAY RECAP *****************************************************************************************************
test1.example.com: ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
test2.example.com: ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
test3.example.com: ok=0    changed=0    unreachable=1    failed=0    skipped=1    rescued=0    ignored=0

and if called via

ansible-playbook unreachable.yml; echo $?
0

Removing the line

  ignore_unreachable: true

would result into

ansible-playbook unreachable.yml; echo $?
4

Please take note of the other answer here and Playbook execution.


But this playbooks exists with 4 if there is any unreachable host(s). (annot.: ... and without using ignore_unreachable: true)

Right, that is the expected behavior.

How can I deal (annot.: avoid?) with exit code 4 without using ignore_unreachable: true?

You can't avoid it since that Return / Exit Code is provided from the ansible-playbook command itself at the very end of the run, not from within the playbook and as far as I understand. Usually I am not linking external content, but in this case you may have a look into Ansible Exit Codes.

Furthermore you might also take advantage from What is the XY problem.

U880D
  • 8,601
  • 6
  • 24
  • 40
  • Thanks, I have tried exact same playbook on my local but it still exists with 4. My ansible version is `2.13.2`. Same version with the remote hosts. – Dentrax Aug 16 '23 at 10:20
  • But what is question, what do you try to achieve with the exit code of `ansible-playbook unreachable.yml; echo $?`? – U880D Aug 16 '23 at 10:22
  • It prints 4 but exits with 0. That's because you append `; echo $?` command. – Dentrax Aug 16 '23 at 10:41
  • No, please see in example [What does `echo $?` do?](https://unix.stackexchange.com/a/501129/266568). – U880D Aug 16 '23 at 10:47
  • Thanks, but it prints `4`. Playbook does not exits with success. – Dentrax Aug 16 '23 at 10:51
  • But what is question, what do you try to achieve with the exit code of `ansible-playbook unreachable.yml; echo $?`? – U880D Aug 16 '23 at 10:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/254933/discussion-between-dentrax-and-u880d). – Dentrax Aug 16 '23 at 10:56