Creating custom dynamic inventory with Ansible using Python

Introduction – Dynamic Inventory

In our previous articles, we have seen how Ansible works? Ansible works against multiple systems in your infrastructure at the same time. It does this by selecting portions of systems listed in Ansible’s inventory, which defaults to being saved in the location /etc/ansible/hosts. You can specify a different inventory file using the -i <path> option on the command line.

Often a user of a configuration management system will want to keep inventory in a different software system. Most infrastructure or systems can be managed with a custom inventory sources likes files, database, cloud scripts…etc. Ansible easily supports all of these options via an external inventory system.

Ansible will accept any kind of executable file as an inventory file, so you can build your own dynamic inventory however you like, as long as you can pass it to Ansible as JSON.

In this article, we will creating custom dynamic inventory from MySQL database with Ansible using Python script. For more information, please visit official documentation of dynamic inventory.

 

Script Conventions – Dynamic Inventory

You could create an executable binary, a script, or anything else that can be run and will output JSON to stdout, and Ansible will call it with the argument --list when you run, as an example, ansible all -i dynamic_inventory_script -m ping.
Each group’s value should be either a hash/dictionary containing a list of each host/IP, potential child groups, and potential group variables, or simply a list of host/IP addresses, like so:

{
    "databases": {
        "hosts": ["host1.devopstechie.com", "host2.devopstechie.com"],
        "vars": {
            "a": true
        }
    },
    "webservers": ["host2.devopstechie.com", "host3.devopstechie.com"]
}

For more information, please visit official documentation of developing dynamic inventory.

Create a Custom Dynamic Inventory in Python

To create a custom dynamic inventory script for demonstration purposes, we have implemented a python script which gets the data from MySQL and will output JSON to stdout. Ansible will use it as an inventory source as long as it returns a JSON structure with the –list option.
Sample Python script:

#!/usr/bin/env python

'''
Example custom dynamic inventory script for Ansible, in Python.
'''

import os
import sys
import argparse
import mysql.connector as mariadb
from collections import defaultdict

try:
    import json
except ImportError:
    import simplejson as json

class DynamicInventory(object):

    def __init__(self, args):
        self.inventory = {}
        self.args = args
        self.mariadb_connection = mariadb.connect(user='ansible', password='ansible', database='inventory')
        self.cursor = self.mariadb_connection.cursor(dictionary=True)
	self.os = []
	self.osgroup = []
	self.result = {}
        # Called with `--list`.
        if self.args.list:
            self.inventory = self.dynamic_inventory()
        # If no groups or vars are present, return an empty inventory.
        else:
            self.inventory = self.empty_inventory()

        print json.dumps(self.inventory);

    # Example inventory for testing.
    def dynamic_inventory(self):
	self.cursor.execute("select h.name as hostname, h.os, g.name as groupname from hosts as h, groups as g where h.group_id = g.id")
        result_set = self.cursor.fetchall()
	
	# Normalize data based on db data.
	result_dict = lambda: defaultdict(result_dict) 
        results_group = result_dict()       
        results_os = result_dict()       
	for row in result_set:
	    results_group[row['groupname']][row['hostname']] = row['hostname']
	    results_os[row['os']][row['hostname']] = row['hostname']
	
	# For groupname wise/all groups
	all_hostlist = []
	for group in results_group:
	    group_hostlist = []	    
	    for host in results_group[group]:
		group_hostlist.append(host)
		all_hostlist.append(host)
	        self.result.update({group : group_hostlist})
        
	self.result.update({'all' : list(set(all_hostlist))})

	# For OS groups
	for os in results_os:
	    os_hostlist = []
            for host in results_os[os]:
		os_hostlist.append(host)
	        self.result.update({os : os_hostlist})

	return self.result
	
	
    # Empty inventory for testing.
    def empty_inventory(self):
        return {'_meta': {'hostvars': {}}}

    def __del__(self):
	self.cursor.close()
	self.mariadb_connection.close()

# Main function
def main():
        # Read command line args
	parser = argparse.ArgumentParser()
        parser.add_argument('--list', action = 'store_true')
        args = parser.parse_args()
	# Get the inventory.
	DynamicInventory(args)


if __name__ == '__main__':
	main()

Run & Usage:
Run the script like as following:

[ansible@localhost ~]$ ansible-playbook -i dynamic_inventory.py example_playbook.yml
# or
[ansible@localhost ~]$ ansible -i dynamic_inventory.py group_name -m ping

Python script run and its sample json output

[ansible@localhost ~]$ ./dynamic_inventory.py --list

output ---
{
	"all": ["localdb", "local", "localhost"],
	"REDHAT": ["local", "localhost"],
	"WINDOWS": ["localdb"],
	"app": ["localhost"],
	"db": ["localdb", "local", "localhost"]
}

 

All the files mentioned in these dynamic inventory examples are available in the ansible-dynamic-inventory-mysql GitHub repository.

 

Avinash Pawar

DevOps Practitioner interested in learning new technologies and interested in sharing the knowledge with others.

More Posts - Website

Follow Me:
TwitterFacebookLinkedInGoogle Plus

Avinash Pawar

DevOps Practitioner interested in learning new technologies and interested in sharing the knowledge with others.

Leave a Reply