HubertNNN
HubertNNN

Reputation: 2101

How to create a list of variables per inventory host in ansible

I have following situation.
I have inventory like this (limited to relevant information):

[management]
management-1 vhost_ip=10.0.0.1

[database]
db-1 vhost_ip=10.0.1.1
db-2 vhost_ip=10.0.1.2

[application]
app-1 vhost_ip=10.0.2.1
app-2 vhost_ip=10.0.2.2

Now I need to create a play like this:

- name: Setup management server
  hosts: management
  become: true
  roles:
    - management
      database_ips:
        - 10.0.1.1
        - 10.0.1.1
      application_ips:
        - 10.0.2.1
        - 10.0.2.1

As you can see my management role need a list of all IPs grouped by role.
I do have those IPs already available inside my inventory.

Is there any way to do a conversion that will extract those IPs and turn them into a list? So I could call it something like this (pseudocode based on laravels higher order lists):

  roles:
    - management
      database_ips: {{ groups['databse']->map->vhost_ip }}
      application_ips: {{ groups['application']->map->vhost_ip }}

Also is it possible to do something similar but with a more complex format like this (mapping vhost_ip from inventory to ip in parameter and alias from inventory to name in parameter):

  roles:
    - management
      database_hosts:
        - name: db-1
          ip: 10.0.1.1
        - name: db-2
          ip: 10.0.1.1

Upvotes: 1

Views: 2025

Answers (2)

Vladimir Botka
Vladimir Botka

Reputation: 68034

Update

Use the utility ansible-inventory

  my_inventory: "{{ lookup('pipe', 'ansible-inventory -i hosts --list --yaml')|from_yaml }}"

gives

  my_inventory:
    all:
      children:
        application:
          hosts:
            app-1:
              vhost_ip: 10.0.2.1
            app-2:
              vhost_ip: 10.0.2.2
        database:
          hosts:
            db-1:
              vhost_ip: 10.0.1.1
            db-2:
              vhost_ip: 10.0.1.2
        management:
          hosts:
            management-1:
              vhost_ip: 10.0.0.1
        ungrouped: {}

Declare the lists

  database_ips: "{{ my_inventory.all.children.database.hosts|json_query('*.vhost_ip') }}"
  application_ips: "{{ my_inventory.all.children.application.hosts|json_query('*.vhost_ip') }}"

give

  database_ips: ['10.0.1.1', '10.0.1.2']
  application_ips: ['10.0.2.1', '10.0.2.2']

Example of a complete playbook for testing

- hosts: all

  vars:

    my_inventory: "{{ lookup('pipe', 'ansible-inventory -i hosts --list --yaml')|from_yaml }}"
    database_ips: "{{ my_inventory.all.children.database.hosts|json_query('*.vhost_ip') }}"
    application_ips: "{{ my_inventory.all.children.application.hosts|json_query('*.vhost_ip') }}"

  tasks:

    - debug:
        var: my_inventory
      run_once: true
    - debug:
        msg: |
          database_ips: {{ database_ips }}
          application_ips: {{ application_ips }}
      run_once: true

Original

Iterate the groups and create a dictionary

  - set_fact:
      d1: "{{ d1|d({})|
              combine({item: dict(_keys|zip(_vals))}) }}"
    loop: "{{ groups.keys()|list|difference(['all', 'ungrouped']) }}"
    vars:
      _keys: "{{ groups[item] }}"
      _vals: "{{ groups[item]|
                 map('extract', hostvars, 'vhost_ip')|
                 list }}"
    run_once: true

gives

  d1:
    application:
      app-1: 10.0.2.1
      app-2: 10.0.2.2
    database:
      db-1: 10.0.1.1
      db-2: 10.0.1.2
    management:
      management-1: 10.0.0.1

The selection of the lists is trivial now

  database_ips: "{{ d1.database.values()|list }}"
  application_ips: "{{ d1.application.values()|list }}"

gives

  database_ips:
    - 10.0.1.1
    - 10.0.1.2

  application_ips:
    - 10.0.2.1
    - 10.0.2.2

The dictionary d1 can be also used to create the variable database_hosts

  database_hosts: "{{ d1.database|
                      dict2items(key_name='name', value_name='ip') }}"

gives

  database_hosts:
    - ip: 10.0.1.1
      name: db-1
    - ip: 10.0.1.2
      name: db-2

Convert the dictionaries into lists if you want to

  - set_fact:
      d2: "{{ d2|d({})|
              combine({item: dict(_keys|zip(_vals))|
                             dict2items(key_name='name', value_name='ip')}) }}"
    loop: "{{ groups.keys()|list|difference(['all', 'ungrouped']) }}"
    vars:
      _keys: "{{ groups[item] }}"
      _vals: "{{ groups[item]|
                 map('extract', hostvars, 'vhost_ip')|
                 list }}"
    run_once: true

gives

  d2:
    application:
      - ip: 10.0.2.1
        name: app-1
      - ip: 10.0.2.2
        name: app-2
    database:
      - ip: 10.0.1.1
        name: db-1
      - ip: 10.0.1.2
        name: db-2
    management:
      - ip: 10.0.0.1
        name: management-1

The usage is trivial

  roles:
    - management
      database_hosts: "{{ d2.database }}"

The dictionary d2 can be also used to create the lists if necessary

  database_ips: "{{ d2.database|map(attribute='ip')|list }}"
  application_ips: "{{ d2.application|map(attribute='ip')|list }}"

Note

You can keep the default groups all and ungrouped

          loop: "{{ groups.keys()|list }}"

You'll get

  d1:
    all:
      app-1: 10.0.2.1
      app-2: 10.0.2.2
      db-1: 10.0.1.1
      db-2: 10.0.1.2
      management-1: 10.0.0.1
    application:
      app-1: 10.0.2.1
      app-2: 10.0.2.2
    database:
      db-1: 10.0.1.1
      db-2: 10.0.1.2
    management:
      management-1: 10.0.0.1
    ungrouped: {}
  d2:
    all:
    - ip: 10.0.0.1
      name: management-1
    - ip: 10.0.1.1
      name: db-1
    - ip: 10.0.1.2
      name: db-2
    - ip: 10.0.2.1
      name: app-1
    - ip: 10.0.2.2
      name: app-2
    application:
    - ip: 10.0.2.1
      name: app-1
    - ip: 10.0.2.2
      name: app-2
    database:
    - ip: 10.0.1.1
      name: db-1
    - ip: 10.0.1.2
      name: db-2
    management:
    - ip: 10.0.0.1
      name: management-1
    ungrouped: []

Upvotes: 2

β.εηοιτ.βε
β.εηοιτ.βε

Reputation: 39129

Yes, this is pretty easy to achieve with the right set of filters applied to the special variable hostvars.

The set of filter we want to apply are:

  • dict2items so we can make a list of the hostvars dictionary in order to filter on it
  • selectattr in order to filter the nodes that are in a specific, with another special variables, groups
  • And finally a map to extract one attribute from each dictionary into a simple list

So, we end up with those variables:

application_ips: >-
  {{
    hostvars  
    | dict2items 
    | selectattr('key', 'in', groups.application) 
    | map(attribute='value.vhost_ip')
  }} 
database_ips: >-
  {{
    hostvars  
    | dict2items 
    | selectattr('key', 'in', groups.database) 
    | map(attribute='value.vhost_ip')
  }}
management_ips: >-
  {{
    hostvars  
    | dict2items 
    | selectattr('key', 'in', groups.management) 
    | map(attribute='value.vhost_ip')
  }}

As for the more complex requirement, you can use a json_query and a JMESPath query.

application_ips: >-
  {{
    hostvars  
    | dict2items 
    | selectattr('key', 'in', groups.application) 
    | json_query('[].{name: key, ip: value.vhost_ip}')
  }} 
database_ips: >-
  {{
    hostvars  
    | dict2items 
    | selectattr('key', 'in', groups.database) 
    | json_query('[].{name: key, ip: value.vhost_ip}')
  }}
management_ips: >-
  {{
    hostvars  
    | dict2items 
    | selectattr('key', 'in', groups.management) 
    | json_query('[].{name: key, ip: value.vhost_ip}')
  }}

Given the playbook:

- hosts: all
  gather_facts: no

  tasks:
    - debug:
        msg: 
          application_ips: "{{ application_ips }}"
          database_ips: "{{ database_ips }}"
          management_ips: "{{ management_ips }}"
      vars:
        application_ips: >-
          {{
            hostvars  
            | dict2items 
            | selectattr('key', 'in', groups.application) 
            | map(attribute="value.vhost_ip")
          }} 
        database_ips: >-
          {{
            hostvars  
            | dict2items 
            | selectattr('key', 'in', groups.database) 
            | map(attribute="value.vhost_ip")
          }}
        management_ips: >-
          {{
            hostvars  
            | dict2items 
            | selectattr('key', 'in', groups.management) 
            | map(attribute="value.vhost_ip")
          }}
      run_once: true
      delegate_to: localhost

    - debug:
        msg: 
          application_ips: "{{ application_ips }}"
          database_ips: "{{ database_ips }}"
          management_ips: "{{ management_ips }}"
      vars:
        application_ips: >-
          {{
            hostvars  
            | dict2items 
            | selectattr('key', 'in', groups.application) 
            | json_query('[].{name: key, ip: value.vhost_ip}')
          }} 
        database_ips: >-
          {{
            hostvars  
            | dict2items 
            | selectattr('key', 'in', groups.database) 
            | json_query('[].{name: key, ip: value.vhost_ip}')
          }}
        management_ips: >-
          {{
            hostvars  
            | dict2items 
            | selectattr('key', 'in', groups.management) 
            | json_query('[].{name: key, ip: value.vhost_ip}')
          }}
      run_once: true
      delegate_to: localhost

This yields:

TASK [debug] ******************************************************
ok: [node2 -> localhost] => 
  msg:
    application_ips:
    - 10.0.2.1
    - 10.0.2.2
    database_ips:
    - 10.0.1.1
    - 10.0.1.2
    management_ips:
    - 10.0.0.1

TASK [debug] ******************************************************
ok: [node2 -> localhost] => 
  msg:
    application_ips:
    - ip: 10.0.2.1
      name: app-1
    - ip: 10.0.2.2
      name: app-2
    database_ips:
    - ip: 10.0.1.1
      name: db-1
    - ip: 10.0.1.2
      name: db-2
    management_ips:
    - ip: 10.0.0.1
      name: management-1

Upvotes: 2

Related Questions