k3it
k3it

Reputation: 2750

Run ansible task only once per each unique fact value

I have a dynamic inventory that assigns a "fact" to each host, called a 'cluster_number'.

The cluster numbers are not known in advance, but there is one or more hosts that are assigned the same number. The inventory has hundreds of hosts and 2-3 dozen unique cluster numbers.

I want to run a task for all hosts in the inventory, however I want to execute it only once per each group of hosts sharing the same 'cluster_number' value. It does not matter which specific host is selected for each group.

I feel like there should be a relatively straight forward way to do this with ansible, but can't figure out how. I've looked at group_by, when, loop, delegate_to etc. But no success yet.

Upvotes: 1

Views: 3078

Answers (2)

Vladimir Botka
Vladimir Botka

Reputation: 68144

An option would be to

  • group_by the cluster_number
  • run_once a loop over cluster numbers
  • and pick the first host from each group.

For example given the hosts

[test]
test01 cluster_number='1'
test02 cluster_number='1'
test03 cluster_number='1'
test04 cluster_number='1'
test05 cluster_number='1'
test06 cluster_number='2'
test07 cluster_number='2'
test08 cluster_number='2'
test09 cluster_number='3'
test10 cluster_number='3'
[test:vars]
cluster_numbers=['1','2','3']

the following playbook

- hosts: all
  gather_facts: no
  tasks:
    - group_by: key=cluster_{{ cluster_number }}
    - debug: var=groups['cluster_{{ item }}'][0]
      loop:  "{{ cluster_numbers }}"
      run_once: true

gives

> ansible-playbook test.yml | grep groups
"groups['cluster_1'][0]": "test01", 
"groups['cluster_2'][0]": "test06", 
"groups['cluster_3'][0]": "test09",

To execute tasks at the targets include_tasks (instead of debug in the loop above) and delegate_to the target

- set_fact:
    my_group: "cluster_{{ item }}"
- command: hostname
  delegate_to: "{{ groups[my_group][0] }}"

Note: Collect the list cluster_numbers from the inventory

cluster_numbers: "{{ hostvars|json_query('*.cluster_number')|unique }}"

Upvotes: 2

Konstantin Suvorov
Konstantin Suvorov

Reputation: 68289

If you don't mind play logs cluttering, here's a way:

- hosts: all
  gather_facts: no
  serial: 1
  tasks:
    - group_by:
        key: "single_{{ cluster_number }}"
      when: groups['single_'+cluster_number] | default([]) | count == 0

- hosts: single_*
  gather_facts: no
  tasks:
    - debug:
        msg: "{{ inventory_hostname }}"

serial: 1 is crucial in the first play to reevaluate when statement on for every host.

After first play you'll have N groups for each cluster with only single host in them.

Upvotes: 1

Related Questions