18
Jun
Adapting inventory for Ansible
via jpmens.net
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:
[devservers]
a1
k4.ww.mens.de
[dbservers]
deb101 ntp=ntp1.example.net
sushi ansible_ssh_host=127.0.0.1 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/inv.py
$ ansible dbserver --list-hosts
pg01
pg02
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 inv.py program
will be invoked like this:
inv.py --list
inv.py --host pg01
inv.py --host pg02
Our little program produces this JSON output from above database, when invoked
with --list:
{
"ungrouped": {
"hosts": [
"ldap"
]
},
"webserver": {
"hosts": [
"www01",
"www02"
]
},
"testing": {
"hosts": [
"t1",
"tiggr"
]
},
"local": [
"127.0.0.1"
],
"dbserver": {
"hosts": [
"pg01",
"pg02"
]
}
}
When Ansible calls the program to find variables for a particular host
(i.e. inv.py --host pg01), the program will produce this JSON on
output:
{
"admin": "Jane Jolie",
"datacenter": 1
}
The inv.py program is simple enough:
#!/usr/bin/env python
import sqlite3
import sys
try:
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/127.0.0.1
inventory['local'] = [ '127.0.0.1' ]
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' : []
}
inventory[group]['hosts'].append(row['name'])
cur.close()
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({})
sys.exit(0)
# 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)
con.row_factory=sqlite3.Row
if len(sys.argv) == 2 and (sys.argv[1] == '--list'):
grouplist(con)
elif len(sys.argv) == 3 and (sys.argv[1] == '--host'):
hostinfo(con, sys.argv[2])
else:
print "Usage: %s --list or --host <hostname>" % sys.argv[0]
sys.exit(1)
con.close()
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 inv.py. For example:
./host_vars
./host_vars/ldap
./inv.py
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
127.0.0.1
ldap
manager
pg01
pg02
t1
t1.prox
tiggr
www01
www02
www1
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.












