Run an Ansible task to all hosts if at least one of the hosts has a variable

Viewed 546

I want to run a task to all hosts if at least one of the hosts has a variable "new_node". For example i have inventory

[all]
host1.example.net
host2.example.net
host3.example.net new_node=True

And if in one of the hosts has variable "new_node=True" then run this task on all hosts

---
- hosts: all

  tasks:
     - name: Create file yep at all hosts
       file:
         path: /tmp/yep
         state: file

What condition or filter should i apply? Any ideas. Thanks

3 Answers

You can create a variable, that will be set to true when one of the hosts in [all] group has the variable new_node defined. Then this variable can be used to conditionally run the task.

Example:

  tasks:
  - set_fact:
      run_on_all: true
    when: hostvars[item]['new_node']|default(false)
    with_items: "{{ groups['all'] }}"

  - file:
      path: /tmp/yep
      state: touch
    when: run_on_all|default(false)

The first task, sets the variable run_on_all to true if any one of the hosts has the variable new_node=True. Then the next task will execute if the previous task set the run_on_all variable to true. I am using the default() filter to avoid the chances of the variables being "undefined".

You did not actually specify what should happen if this property was not present, so I will assume you would simply want to abort the play in that case.

Simplest solution that I could think of is to set the playbook to run against the group [all], and check if this property is present on any of the hosts. If it is not present on any hosts, you abort the play.

Example

---
- hosts: all

  tasks:
    - name: Check if the hostvar 'new_node' is present on any hosts
      set_fact:
        has_new_node: "{{ groups['all'] | map('extract', hostvars, 'new_node') | list | select('defined') | length | bool }}"
      delegate_to: localhost
    
    - name: Abort play if not 'has_new_node'
      meta: end_play
      when: not has_new_node

    - debug:
        msg: new_node found

extract the variables, select true items and evaluate the test is any, e.g.

    - set_fact:
        semaphore: "{{ ansible_play_hosts_all|
                       map('extract', hostvars, 'new_node')|
                       select is any }}"
      run_once: true

gives true if any variable new_node is true and gives false otherwise

  semaphore: true

Then use the variable semaphore in the conditions e.g.

      when: semaphore|bool

Filter select explained

Quoting from select

"If no test is specified, each object will be evaluated as a boolean."

Given the inventory

shell> cat hosts
host1.example.net
host2.example.net new_node=False
host3.example.net new_node=True

The task extracts the variables

    - debug:
        msg: "{{ ansible_play_hosts_all|
                 map('extract', hostvars, 'new_node')|
                 list }}"
  msg: '[Undefined, False, True]'

We don't have to care about Undefined because select() evaluates Undefined to False

    - debug:
        msg: "{{ ansible_play_hosts_all|
                 map('extract', hostvars, 'new_node')|
                 select|list }}"
  msg:
  - true

You can test the evaluation of Undefined to False separately, e.g.

    - debug:
        msg: "{{ ansible_play_hosts_all|
                 map('extract', hostvars, 'new_node')|
                 map('bool')|
                 list }}"

gives

  msg:
  - false
  - false
  - true

If no variable is defined

shell> cat hosts
host1.example.net
host2.example.net
host3.example.net

select returns an empty list

  msg: '[Undefined, Undefined, Undefined]'
  msg: []
Related