3

I have a the following oversimplified ansible playbook:


- name: Prepare worker nodes
  hosts: "{{ hosts }}"
  serial:
    - 1
    - 3
  remote_user: root
  any_errors_fatal: true
  vars:
    hosts: nodes
    reboot: false
  tasks:

    - pause:
        prompt: "Reboot server(s) to make sure things are working during setup? (Y/n)"
        echo: true
      register: confirm_reboot
      tags: [ untagged, hostname, netplan, firewalld ]

    - set_fact:
        reboot: "{{ (confirm_reboot.user_input == '' or confirm_reboot.user_input == 'Y' or confirm_reboot.user_input == 'y' ) | ternary('True', 'False') }}"
      tags: [ untagged, hostname, netplan, firewalld, firewalld-install, firewalld-config ]

    - debug:
        msg: "{{ reboot }}"

It asks for the user's input so it can decide on some reboot policies. This works just fine when you have just one node, but when you have multiple nodes it will prompt for each one. Suppose you have 42 nodes -- it will ask you 42 times.

I'm trying to figure out if there is an easy way to make the prompt appear just once and share the result among the nodes. Maybe I have missed something in the docs?

Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
tftd
  • 16,203
  • 11
  • 62
  • 106
  • Possible duplicate of https://stackoverflow.com/questions/22070232/how-to-get-an-ansible-check-to-run-only-once-in-a-playbook – David Jones Sep 04 '21 at 22:31
  • Does this answer your question? [How to get an Ansible check to run only once in a playbook?](https://stackoverflow.com/questions/22070232/how-to-get-an-ansible-check-to-run-only-once-in-a-playbook) – David Jones Sep 04 '21 at 22:31
  • @djones not a duplicate - I've tried using `run_once: yes` but it still asks the question every X hosts which is very strange. P.S. I've also tried with `pre_tasks` and the result is the same. – tftd Sep 04 '21 at 22:36

2 Answers2

2

Given the inventory

shell> cat hosts
[test]
host1
host2
host3
host4
host5

the playbook

shell> cat playbook.yml
---
- hosts: test
  serial:
    - 1
    - 3
  gather_facts: false
  tasks:
    - pause:
        prompt: "Reboot? (Y/n)"
        echo: true
      register: confirm_reboot
      run_once: true
    - debug:
        msg: "Reboot {{ inventory_hostname }}"
      when: confirm_reboot.user_input|lower == 'y'

works as expected

shell> ansible-playbook -i hosts playbook.yml

PLAY [test] *********************************

TASK [pause] ********************************
[pause]
Reboot? (Y/n):
ok: [host1]

TASK [debug] ********************************
ok: [host1] => 
  msg: Reboot host1

PLAY [test] *********************************

TASK [pause] ********************************
[pause]
Reboot? (Y/n):
ok: [host2]

TASK [debug] ********************************
ok: [host2] => 
  msg: Reboot host2
ok: [host3] => 
  msg: Reboot host3
ok: [host4] => 
  msg: Reboot host4

PLAY [test] *********************************

TASK [pause] ********************************
[pause]
Reboot? (Y/n):
ok: [host5]

TASK [debug] ********************************
ok: [host5] => 
  msg: Reboot host5

Q: "Require the input just once for the entire playbook and be propagated to all hosts."

A: Split the playbook, e.g.

shell> cat playbook.yml
---
- hosts: test
  gather_facts: false
  tasks:
    - pause:
        prompt: "Reboot? (Y/n)"
        echo: true
      register: confirm_reboot
      run_once: true

- hosts: test
  serial:
    - 1
    - 3
  gather_facts: false
  tasks:
    - debug:
        msg: "Reboot {{ inventory_hostname }}"
      when: confirm_reboot.user_input|lower == 'y'

the variable from the first play will be shared among all hosts in the second play

shell> ansible-playbook -i hosts playbook.yml

PLAY [test] *********************************

TASK [pause] ********************************
[pause]
Reboot? (Y/n):
ok: [host1]

PLAY [test] *********************************

TASK [debug] ********************************
ok: [host1] =>
  msg: Reboot host1

PLAY [test] *********************************

TASK [debug] ********************************
ok: [host3] =>
  msg: Reboot host3
ok: [host2] =>
  msg: Reboot host2
ok: [host4] =>
  msg: Reboot host4

PLAY [test] *********************************

TASK [debug] ********************************
ok: [host5] =>
  msg: Reboot host5
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63
  • Your output suggests the user input was required 3 times. What I need is for the input to be required just once for the entire playbook and be propagated to all hosts. :) – tftd Sep 06 '21 at 01:43
  • This is an interesting more clean and easier to read approach -- I'll mark it as the accepted answer. Thanks for sharing! :) – tftd Sep 06 '21 at 11:57
0

It looks like the only way this will work is by using delegate_to and delegate_facts. I came up with something like this:

- name: Prepare worker nodes
  hosts: "{{ hosts }}"
  serial:
    - 1
    - 3
  remote_user: root
  any_errors_fatal: true
  vars:
    hosts: nodes
    reboot: true
  pre_tasks:
    - pause:
        prompt: "Reboot server(s) to make sure things are working during setup? (Y/n)"
        echo: true
      register: confirm_reboot
      run_once: true
      delegate_to: localhost
      delegate_facts: true
      tags: [ untagged, hostname, netplan, firewalld, firewalld-install, firewalld-config ]
      when: "'reboot' not in hostvars['localhost']"

    - set_fact:
        reboot: "{{ (confirm_reboot.user_input == '' or confirm_reboot.user_input == 'Y' or confirm_reboot.user_input == 'y' ) | ternary('True', 'False') }}"
      run_once: true
      delegate_to: localhost
      delegate_facts: true
      tags: [ untagged, hostname, netplan, firewalld, firewalld-install, firewalld-config ]
      when: "'reboot' not in hostvars['localhost']"

    - set_fact:
        reboot: "{{ hostvars['localhost']['reboot'] }}"
      run_once: true

  tasks:
    - debug:
        msg: "{{ hostvars['localhost'] }}"
      tags: [ untagged, hostname, netplan, firewalld, firewalld-install, firewalld-config ]

    - debug:
        msg: "{{ reboot }}"
      tags: [ untagged, hostname, netplan, firewalld, firewalld-install, firewalld-config ]

This works by delegating the fact to the localhost (control node) and then it uses it by reference that seems to be kept between the different nodes. It is a hackish workaround to me, but since I don't have that much time to dig deeper into the "why", it'll have to do for now.

If anybody figures out a better way - feel free to post your answer.

tftd
  • 16,203
  • 11
  • 62
  • 106