Build fact dynamically based on variables of other hosts

Viewed 38

I am creating a role in Ansible to let mongodb cluster with other machines. I am using the mongodb_replicaset collection for this use case.

I want to build the member list of the cluster with Ansible.

Imagine there are 7 hosts in the play. The variables are set like this:

host-1 # secondary
host-2 # secondary
host-3 # secondary
host-4 # primary
host-5 # arbiter
host-6 # arbiter
host-7 # secondary

How can I configure Ansible so that it will build the members list based on the amount of hosts in the play, and set a priority based on the role. In this case, host-4 should contain the variable, because that is the primary and will eventually execute the task.

E.g. primary = 1, secondary = 0.5 and arbiter = 0.1

Desired output:

    - host: host-1
      priority: 0.5
    - host: host-2
      priority: 0.5
    - host: host-3
      priority: 0.5
    - host: host-3
      priority: 0.5
    - host: host-4
      priority: 1.0
    - host: host-5
      priority: 0.1
    - host: host-6
      priority: 0.1
    - host: host-7
      priority: 0.5
2 Answers

Define a dictionary with the weight:

member_weight: 
  primary: 1.0
  secondary: 0.5
  arbiter: 0.1

Then, given that your "type of member" is registered under a fact member_type on the host, loop on the hosts in the play:

- set_fact:
    members: >-
      {{ 
         members | default([]) + 
         [{
           'host': item, 
           'priority': member_weight[hostvars[item].member_type]
         }] 
      }}  
  loop: "{{ ansible_play_hosts }}"
  vars:
    member_weight: 
      primary: 1.0
      secondary: 0.5
      arbiter: 0.1

Given the inventory:

all:
  children:
    nodes:
      hosts:
        host-1:
          member_type: secondary
        host-2:
          member_type: secondary
        host-3:
          member_type: secondary
        host-4:
          member_type: primary
        host-5:
          member_type: arbiter
        host-6:
          member_type: arbiter
        host-7:
          member_type: secondary

And the playbook:

- hosts: nodes
  gather_facts: no

  tasks:
    - set_fact:
        members: >-
          {{ 
            members | default([]) + 
            [{
              'host': item, 
              'priority': member_weight[hostvars[item].member_type]
            }] 
          }}  
      loop: "{{ ansible_play_hosts }}"
      run_once: true
      vars:
        member_weight: 
          primary: 1.0
          secondary: 0.5
          arbiter: 0.1

    - debug:
        var: members
      run_once: true

This yields:

TASK [set_fact] ***************************************************************
ok: [host-1] => (item=host-1)
ok: [host-1] => (item=host-2)
ok: [host-1] => (item=host-3)
ok: [host-1] => (item=host-4)
ok: [host-1] => (item=host-5)
ok: [host-1] => (item=host-6)
ok: [host-1] => (item=host-7)

TASK [debug] ******************************************************************
ok: [host-1] => 
  members:
  - host: host-1
    priority: 0.5
  - host: host-2
    priority: 0.5
  - host: host-3
    priority: 0.5
  - host: host-4
    priority: 1.0
  - host: host-5
    priority: 0.1
  - host: host-6
    priority: 0.1
  - host: host-7
    priority: 0.5

Given the inventory

shell> cat hosts
host-1 member_type=secondary
host-2 member_type=secondary
host-3 member_type=secondary
host-4 member_type=primary
host-5 member_type=arbiter
host-6 member_type=arbiter
host-7 member_type=secondary

Create the dictionary members

- hosts: all
  gather_facts: false
  vars:
    member_weight:
      primary: 1.0
      secondary: 0.5
      arbiter: 0.1
  tasks:
    - set_fact:
        priority: "{{ member_weight[member_type] }}"
    - set_fact:
        members: "{{ dict(ansible_play_hosts|zip(_priorities)) }}"
      vars:
        _priorities: "{{ ansible_play_hosts|
                         map('extract', hostvars, 'priority')|
                         map('float')|list }}"
      run_once: true

gives

  members:
    host-1: 0.5
    host-2: 0.5
    host-3: 0.5
    host-4: 1.0
    host-5: 0.1
    host-6: 0.1
    host-7: 0.5

Use dict2items if you need the list. For example

    - set_fact:
        members_list: "{{ members|dict2items(key_name='host',
                                             value_name='priority') }}"
      run_once: true

gives

  members_list:
  - host: host-1
    priority: 0.5
  - host: host-2
    priority: 0.5
  - host: host-3
    priority: 0.5
  - host: host-4
    priority: 1.0
  - host: host-5
    priority: 0.1
  - host: host-6
    priority: 0.1
  - host: host-7
    priority: 0.5
Related