How to create a one-time user prompt input in ansible?

Viewed 795

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?

2 Answers

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

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.

Related