Ansible uses a so-called "inventory" to determine the list of nodes and groups of nodes it can use. This inventory file defaults to /etc/ansible/hosts and typically looks something like this in INI file format:


sushi ansible_ssh_host= ansible_ssh_port=222

The above example defines two groups (devservers and dbservers) each with the specified host names, which need to be resolvable from the Ansible management system. If you cater for special configurations (e.g. Vagrant boxes on your workstation, or something behind a jump-host) you can use the ansible_ssh_* variables to define particular addresses and/or port numbers to use.

If you prefer to separate out, say, "production" and "development" systems, you can also have two distinct inventory files which you pass to ansible and ansible-playbook with the -i switch, or by setting $ANSIBLE_HOSTS to point to the respective file.

A less known fact is that the inventory can also be read from a program. Say you already have a configuration management database (CMDB) and wish to use that for driving Ansible, it's pretty easy to do. While you can, for example, periodically dump that database into a "hosts" file for Ansible, you can also have Ansible query your database on the fly.

I accomplish this by setting $ANSIBLE_HOSTS to the full pathname of the executable program which will provide the inventory.

A small example

As a small example, consider the following SQLite table with the three columns id, type, and name:

sqlite> SELECT * FROM hosts;
1           webserver   www01
2           dbserver    pg01
3           dbserver    pg02
4           webserver   www02
5           testing     tiggr
6           testing     t1
7                       ldap

I want to massage the type column into a group for Ansible, whereby NULL types will be placed into a group with the exciting name "ungrouped".

Let's see some results.

$ export ANSIBLE_HOSTS=/etc/ansible/inventory/
$ ansible dbserver --list-hosts

The group name "dbserver" has been expanded and Ansible shows me the names of the two hosts it contains.

Ansible invokes the inventory script at least twice: once to find all groups and the hosts they contains, and once for each host. In other words, when Ansible starts doing something to the "dbserver" group, our program will be invoked like this: --list --host pg01 --host pg02

Our little program produces this JSON output from above database, when invoked with --list:

    "ungrouped": {
        "hosts": [
    "webserver": {
        "hosts": [
    "testing": {
        "hosts": [
    "local": [
    "dbserver": {
        "hosts": [

When Ansible calls the program to find variables for a particular host (i.e. --host pg01), the program will produce this JSON on output:

    "admin": "Jane Jolie", 
    "datacenter": 1

The program is simple enough:

#!/usr/bin/env python

import sqlite3
import sys
    import json
except ImportError:
    import simplejson as json

dbname = '/etc/inv.db'

def grouplist(conn):

    inventory ={}

    # Add group for [local] (e.g. local_action). If needed,
    # set ansible_python_interpreter in host_vars/
    inventory['local'] = [ '' ]

    cur = conn.cursor()
    cur.execute("SELECT type, name FROM hosts ORDER BY 1, 2")

    for row in cur.fetchall():
        group = row['type']
        if group is None:
            group = 'ungrouped'
        # Add group with empty host list to inventory{} if necessary
        if not group in inventory:
            inventory[group] = {
                'hosts' : []

    print json.dumps(inventory, indent=4)

def hostinfo(conn, name):

    vars = {}

    cur = conn.cursor()
    cur.execute("SELECT COUNT(*) FROM hosts WHERE name=?", (name, ))

    row = cur.fetchone()
    if row[0] == 0:
        print json.dumps({})

    # Inject some variables for all hosts
    vars = {
        'admin'         : 'Jane Jolie',
        'datacenter'    : 1

    # Assuming you *know* that certain hosts need special vars
    # and you can't or don't want to use host_vars/ group_vars,
    # you could specify them here. For example, I *know* that
    # hosts with the word 'ldap' in them need a base DN

    if 'ldap' in name.lower():
        vars['baseDN'] = 'dc=mens,dc=de'

    print json.dumps(vars, indent=4)

if __name__ == '__main__':
    con = sqlite3.connect(dbname)

    if len(sys.argv) == 2 and (sys.argv[1] == '--list'):
    elif len(sys.argv) == 3 and (sys.argv[1] == '--host'):
        hostinfo(con, sys.argv[2])
        print "Usage: %s --list or --host <hostname>" % sys.argv[0]


As alluded to in the comments, I can create variables which Ansible will use in, say, templates. I could obtain these from our CMDB, pull them from files, etc. I could easily also create special groups by injecting appropriate JSON. For example, a customer of mine wanted groups defined based on the kind of hardware and its location; a few lines of Python did the trick.

Note, that due to the inventory program being invoked frequently, it will impose an additional load on your CMDB. (There is talk of modifying Ansible to not have that done, but it's a work in progress.)

If necessary, you could employ some form of caching such as the ec2 inventory script uses.

Dynamic inventory and more vars

There are loads of places from which Ansible reads variables if you want them, and using inventory scripts, such as shown above, adds yet another source.

In addition to whichever variables my inventory script produces, Ansible will also populate host_vars and group_vars from the respective directories if these are placed in the directory containing my For example:


When the node "ldap" is referenced (i.e. used), Ansible will populate additional variables from the host_vars/ldap file -- a file in YAML format.

Mixing static and dynamic

In addition to inventory scripts, I can set $ANSIBLE_HOSTS to point to a directory. In this case, Ansible runs any executable files it finds therein and merges static inventory files (in INI file format) it finds into their output.

$ export ANSIBLE_HOSTS=/etc/ansible/inventory
$ ansible all --list-hosts

You'll notice there are more nodes shown than in the first JSON output above (www1 and manager, for example) -- these come from additional inventory files in /etc/ansible/inventory/.

Most of this is well documented on the AnsibleWorks Web site. Nevertheless, I hope this was useful to you.


blog comments powered by Disqus