How can I get a list of hosts from an Ansible inventory file?

Question:

Is there a way to use the Ansible Python API to get a list of hosts from a given inventory file / group combination?

For example, our inventory files are split up by service type:

[dev:children]
dev_a
dev_b

[dev_a]
my.host.int.abc.com

[dev_b]
my.host.int.xyz.com


[prod:children]
prod_a
prod_b

[prod_a]
my.host.abc.com

[prod_b]
my.host.xyz.com

Can I use ansible.inventory in some way to pass in a specific inventory file, and the group I want to act on, and have it return a list of hosts that match?

Asked By: MrDuk

||

Answers:

Do the same trick from before, but instead of all, pass the group name you want to list:

ansible (group name here) -i (inventory file here) --list-hosts

Answered By: nitzmahone

I was struggling with this as well for awhile, but found a solution through trial & error.

One of the key advantages to the API is that you can pull variables and metadata, not just hostnames.

Starting from Python API – Ansible Documentation:

#!/usr/bin/env python
#  Ansible: initialize needed objects
variable_manager = VariableManager()
loader = DataLoader()

#  Ansible: Load inventory
inventory = Inventory(
    loader = loader,
    variable_manager = variable_manager,
    host_list = 'hosts', # Substitute your filename here
)

This gives you an Inventory instance, which has methods and properties to provide groups and hosts.

To expand further (and provide examples of Group and Host classes), here’s a snippet I wrote which serializes the inventory as a list of groups, with each group having a ‘hosts’ attribute that is a list of each host’s attributes.

#/usr/bin/env python
def serialize(inventory):
    if not isinstance(inventory, Inventory):
        return dict()

    data = list()
    for group in inventory.get_groups():
        if group != 'all':
            group_data = inventory.get_group(group).serialize()

            #  Seed host data for group
            host_data = list()
            for host in inventory.get_group(group).hosts:
                host_data.append(host.serialize())

            group_data['hosts'] = host_data
            data.append(group_data)

    return data

#  Continuing from above
serialized_inventory = serialize(inventory)

I ran this against my lab of four F5 BIG-IP’s, and this is the result (trimmed):

<!-- language: lang-json -->
[{'depth': 1,
  'hosts': [{'address': u'bigip-ve-03',
             'name': u'bigip-ve-03',
             'uuid': UUID('b5e2180b-964f-41d9-9f5a-08a0d7dd133c'),
             'vars': {u'hostname': u'bigip-ve-03.local',
                      u'ip': u'10.128.1.130'}}],
  'name': 'ungrouped',
  'vars': {}},
 {'depth': 1,
  'hosts': [{'address': u'bigip-ve-01',
             'name': u'bigip-ve-01',
             'uuid': UUID('3d7daa57-9d98-4fa6-afe1-5f1e03db4107'),
             'vars': {u'hostname': u'bigip-ve-01.local',
                      u'ip': u'10.128.1.128'}},
            {'address': u'bigip-ve-02',
             'name': u'bigip-ve-02',
             'uuid': UUID('72f35cd8-6f9b-4c11-b4e0-5dc5ece30007'),
             'vars': {u'hostname': u'bigip-ve-02.local',
                      u'ip': u'10.128.1.129'}},
            {'address': u'bigip-ve-04',
             'name': u'bigip-ve-04',
             'uuid': UUID('255526d0-087e-44ae-85b1-4ce9192e03c1'),
             'vars': {}}],
  'name': u'bigip',
  'vars': {u'password': u'admin', u'username': u'admin'}}]
Answered By: Theo

I had a similar problem and I think nitzmahone’s approach of not using unsupported calls to the Python API. Here’s a working solution, relying on the nicely JSON-formatted output of ansible-inventory CLI:

pip install ansible==2.4.0.0 sh==1.12.14

An example inventory file, inventory/qa.ini:

[lxlviewer-server]
id-qa.kb.se

[xl_auth-server]
login.libris.kb.se

[export-server]
export-qa.libris.kb.se

[import-server]
import-vcopy-qa.libris.kb.se

[rest-api-server]
api-qa.libris.kb.se

[postgres-server]
pgsql01-qa.libris.kb.se

[elasticsearch-servers]
es01-qa.libris.kb.se
es02-qa.libris.kb.se
es03-qa.libris.kb.se

[tomcat-servers:children]
export-server
import-server
rest-api-server

[flask-servers:children]
lxlviewer-server
xl_auth-server

[apache-servers:children]
lxlviewer-server

[nginx-servers:children]
xl_auth-server

A Python 2.7 function to extract info (easily extendable to hostvars et cetera):

import json
from sh import Command

def _get_hosts_from(inventory_path, group_name):
    """Return list of hosts from `group_name` in Ansible `inventory_path`."""
    ansible_inventory = Command('ansible-inventory')
    json_inventory = json.loads(
        ansible_inventory('-i', inventory_path, '--list').stdout)

    if group_name not in json_inventory:
        raise AssertionError('Group %r not found.' % group_name)

    hosts = []
    if 'hosts' in json_inventory[group_name]:
        return json_inventory[group_name]['hosts']
    else:
        children = json_inventory[group_name]['children']
        for child in children:
            if 'hosts' in json_inventory[child]:
                for host in json_inventory[child]['hosts']:
                    if host not in hosts:
                        hosts.append(host)
            else:
                grandchildren = json_inventory[child]['children']
                for grandchild in grandchildren:
                    if 'hosts' not in json_inventory[grandchild]:
                        raise AssertionError('Group nesting cap exceeded.')
                    for host in json_inventory[grandchild]['hosts']:
                        if host not in hosts:
                            hosts.append(host)
        return hosts

Proof that it works (also with child and grandchild groups):

In [1]: from fabfile.conf import _get_hosts_from

In [2]: _get_hosts_from('inventory/qa.ini', 'elasticsearch-servers')
Out[2]: [u'es01-qa.libris.kb.se', u'es02-qa.libris.kb.se', u'es03-qa.libris.kb.se']

In [3]: _get_hosts_from('inventory/qa.ini', 'flask-servers')
Out[3]: [u'id-qa.kb.se', u'login.libris.kb.se']

In [4]:
Answered By: mblomdahl

For me following worked

from ansible.parsing.dataloader import DataLoader
from ansible.inventory.manager import InventoryManager

if __name__ == '__main__':
    inventory_file_name = 'my.inventory'
    data_loader = DataLoader()
    inventory = InventoryManager(loader = data_loader,
                             sources=[inventory_file_name])

    print(inventory.get_groups_dict()['spark-workers'])

inventory.get_groups_dict() returns a dictionary that you can use to get hosts by using the group_name as key as shown in the code.
You will have to install ansible package you can do by pip as follows

pip install ansible
Answered By: smishra

There has been changes to Ansible API since the approved answer:

This works for Ansible 2.8 (and maybe more)

Here’s the way I was able to access most of the data:

from ansible.parsing.dataloader import DataLoader
from ansible.inventory.manager import InventoryManager


loader = DataLoader()
# Sources can be a single path or comma separated paths
inventory = InventoryManager(loader=loader, sources='path/to/file')

# My use case was to have all:vars as the 1st level keys, and have groups as key: list pairs.
# I also don't have anything ungrouped, so there might be a slightly better solution to this.
# Your use case may be different, so you can customize this to how you need it.
x = {}
ignore = ('all', 'ungrouped')
x.update(inventory.groups['all'].serialize()['vars'])
group_dict = inventory.get_groups_dict()

for group in inventory.groups:
    if group in ignore:
        continue
    x.update({
        group: group_dict[group]
    })

Example:

Input:

[all:vars]
x=hello
y=world

[group_1]
youtube
google

[group_2]
stack
overflow

Output:

{"x":"hello","y":"world","group_1":["youtube","google"],"group_2":["stack","overflow"]}

Again, your use case might be different than mine, so you’ll have to slightly change the code to how you want it to be.

Answered By: NinjaKitty

With ansible 2.4 and more, use ansible-inventory executable.

With ansible 2.3 or less, use the following solution instead.

#! /usr/bin/python
# vi ansible-inventory
# Ansible 2.3- / Python 2.7 - use build-in Ansible Inventory 
import argparse
parser=argparse.ArgumentParser(description='List hosts in inventory as seen by Ansible.')
parser.add_argument('-i', dest='inventory_file', default=''
                   , help='Inventory filename', required=True)
args=parser.parse_args() ;
 
# For ansible 2.4, use this instead.
#from ansible.inventory.manager import Inventory
from ansible.parsing.dataloader import DataLoader
from ansible.inventory import Inventory
from ansible.vars import VariableManager

variable_manager=VariableManager()
data_loader=DataLoader()
inventory=Inventory(loader = data_loader 
                   , variable_manager=variable_manager
                   , host_list=args.inventory_file)
import json
print json.dumps(inventory.get_group_dict(), indent=4)

With this file in the path here above in $PATH as ansible-inventory, assuming inventory directory is production,

# ansible-inventory -i production
Answered By: MUY Belgium
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.