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?
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
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'}}]
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]:
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
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.
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
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?
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
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'}}]
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]:
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
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.
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