2
votes

Using Ansible v2.9.12

Imagine 4 hosts in a play, with the following vars:

# host1
my_var:
  one: true
  two: master

# host2
my_var:
  one: false
  two: master

# host3
my_var:
  one: false
  two: master

# host4
my_var:
  one: false
  two: arbiter

I want to execute a task on a single host, which is has my_var.two == 'master' and has my_var.one set to false. In this case it would relate to either host2 or host3.

Now, this won't work:

- shell: echo
  when: 
    - my_var.two == 'master'
    - not my_var.one
   run_once: true

Because Ansible executes the task only on the first host in the group, which most likely relates to host1, which is undesired. I'm also fiddling around in the answer described in my previous question, which is somewhat related, but without avail.

1

1 Answers

3
votes

Nothing stops you, in a playbook, to have multiple plays:

- hosts: all
  gather_facts: no
      
  tasks:
    - debug: 
        msg: "{{ my_var }}"

- hosts: host2
  gather_facts: no
      
  tasks:
    - debug: 
        msg: "{{ my_var }}"
      # ^--- Off course, now the when become superfluous, 
      #      since we target a host that we know have those conditions

- hosts: all
  gather_facts: no

  tasks:
    - debug:
        msg: "Keep on going with all hosts"

Would give the recap:

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

TASK [debug] ********************************************************************************************************
ok: [host1] => {
    "msg": {
        "one": true,
        "two": "master"
    }
}
ok: [host2] => {
    "msg": {
        "one": false,
        "two": "master"
    }
}
ok: [host3] => {
    "msg": {
        "one": false,
        "two": "master"
    }
}
ok: [host4] => {
    "msg": {
        "one": false,
        "two": "arbiter"
    }
}

PLAY [host2] ********************************************************************************************************

TASK [debug] ********************************************************************************************************
ok: [host2] => {
    "msg": {
        "one": false,
        "two": "master"
    }
}

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

TASK [debug] ********************************************************************************************************
ok: [host1] => {
    "msg": "Keep on going with all hosts"
}
ok: [host2] => {
    "msg": "Keep on going with all hosts"
}
ok: [host3] => {
    "msg": "Keep on going with all hosts"
}
ok: [host4] => {
    "msg": "Keep on going with all hosts"
}

PLAY RECAP **********************************************************************************************************
host1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
host2                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
host3                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
host4                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

Alternatively, for a more dynamic solution, you could select the correct host, looping through your hosts variables via the magic variable hostvars and, then, delegate the task to this host with delegate_to.

Given the playbook:

- hosts: all
  gather_facts: no
      
  tasks:
    - set_fact: 
        host_master_not_one: "{{ item }}"
      when:
        - host_master_not_one is not defined
        - not hostvars[item].my_var.one
        - "'master' == hostvars[item].my_var.two"
      run_once: true
      loop: "{{ ansible_play_hosts }}" 
    
    - debug: 
        msg: "Running only once, on host {{ host_master_not_one }}"
      delegate_to: "{{ host_master_not_one }}"
      run_once: true

It gives the recap:

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

TASK [set_fact] *****************************************************************************************************
skipping: [host1] => (item=host1) 
ok: [host1] => (item=host2)
skipping: [host1] => (item=host3) 
skipping: [host1] => (item=host4) 

TASK [debug] ********************************************************************************************************
ok: [host1 -> host2] => {
    "msg": "Running only once, on host host2"
}

PLAY RECAP **********************************************************************************************************
host1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

The important part of this recap would be the [host1 -> host2] that indicates that the task was delegated and so run on host2.


Or even, on the same train of thoughts, skip hosts that does not match the one having those said conditions:

- hosts: all
  gather_facts: no
      
  tasks:
    - set_fact: 
        host_master_not_one: "{{ item }}"
      when:
        - host_master_not_one is not defined
        - not hostvars[item].my_var.one
        - "'master' == hostvars[item].my_var.two"
      run_once: true
      loop: "{{ ansible_play_hosts }}" 
    
    - debug: 
        msg: "Running only once, on host {{ host_master_not_one }}"
      when: inventory_hostname == host_master_not_one

Gives the recap:


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

TASK [set_fact] *****************************************************************************************************
skipping: [host1] => (item=host1) 
ok: [host1] => (item=host2)
skipping: [host1] => (item=host3) 
skipping: [host1] => (item=host4) 

TASK [debug] ********************************************************************************************************
skipping: [host1]
ok: [host2] => {
    "msg": "Running only once, on host host2"
}
skipping: [host3]
skipping: [host4]

PLAY RECAP **********************************************************************************************************
host1                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
host2                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
host3                      : ok=0    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
host4                      : ok=0    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0