Skip to content

Instantly share code, notes, and snippets.

@romanlv
Last active December 13, 2016 08:40
Show Gist options
  • Save romanlv/73362c4e9dd535834b84 to your computer and use it in GitHub Desktop.
Save romanlv/73362c4e9dd535834b84 to your computer and use it in GitHub Desktop.
Ansible Dynamic Inventory - join static files

this script allows to combine hosts in different files and run the same playbook on all of them

************ Attention!

It works only if your playbook does not rely on group variable files

example

Inventory1

[appservers]
first.com

[client-live:children]
appservers

[client-live:vars]
y=2

Inventory2

[appservers]
second.com

[client-live:children]
appservers

[client-live:vars]
x=1

script to return combined json

find inventory/ -type f -name "Inventory*" -print0 \
	| xargs -0 python inventory/join_inventory.py $@ 

which would return something like this (not exact)

{
  appservers: {
    hosts: ['first.com', 'second.com']
  }, 
  client-live: {
    children: ['appservers'],
    vars: {
      x: 1, 
      y: 2
    }
  }
}

means your variables are messed up completely

Common problems:

make sure that bash file is executable chmod +x test_sites.sh

#!/usr/bin/env python
import argparse
import os
import re
import json
from ansible.inventory import InventoryParser
class CombinedInventory(object):
def __init__(self):
""" Main execution path """
self.inventory = dict() # A list of groups and the hosts in that group
self.var_names = set()
# self.cache = dict() # Details about hosts in the inventory
# Read settings and parse CLI arguments
self.parse_cli_args()
self.files = self.args.files # ['newleaf-demo', 'ams-test'] # todo, make dynamic
# Cache
self.update_cache()
data_to_print = ""
# Data to print
if self.args.host:
data_to_print = self.get_host_info()
elif self.args.list:
# Display list of instances for inventory
data_to_print = self.json_format_dict(self.inventory, True)
else: # default action with no options
data_to_print = self.json_format_dict(self.inventory, True)
print data_to_print
def parse_cli_args(self):
""" Command line argument processing """
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Cobbler')
parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
parser.add_argument('--host', action='store', help='Get all the variables about a specific instance')
parser.add_argument("files",nargs="*", help='list of files to collect data from')
# parser.add_argument('--files', type=str, )
# parser.add_argument('--refresh-cache', action='store_true', default=False,
# help='Force refresh of cache by making API requests to cobbler (default: False - use cache files)')
self.args = parser.parse_args()
# OVERWRITE_UNSET_VARS = False
def get_host_info(self):
""" Get variables about a specific host """
default_vars = {}
if not self.args.host in self.cache:
# host might not exist anymore
return self.json_format_dict(default_vars, True)
host_info = self.cache[self.args.host]
# if self.OVERWRITE_UNSET_VARS:
# self._overwrite_unset_vars(host_info)
return self.json_format_dict(host_info, True)
# ! not true
# def _overwrite_unset_vars(self, info):
# """ due to bug in current version of ansible (v1.8.1)
# undefined variables that are defined for other hosts of the same group
# ends up in the context of the current task, this is a way to overwrite them"""
# for name in self.var_names:
# if not name in info:
# info[name] = None
def update_cache(self):
""" Make calls to cobbler and save the output in a cache """
self.cache = dict()
self.groups = dict()
self.hosts = dict()
self.var_names = set()
for filename in self.files:
parser = InventoryParser(filename)
for groupname, group in parser.groups.items():
if groupname in ['all']:
continue
group_obj = {}
group_obj['hosts'] = [h.name for h in group.get_hosts()]
variables = group.get_variables()
group_obj['vars'] = variables
# groups_obj['vars'] is not assigned, since different inventories may have conflicting values
group_obj['children'] = [g.name for g in group.child_groups]
self._set_host_vars(group_obj, variables)
if groupname in self.inventory:
# merging values
for key in ['hosts', 'children']:
self.inventory[groupname][key] = list(set(self.inventory[groupname][key] + group_obj[key]))
else:
self.inventory[groupname] = group_obj
def _set_host_vars(self, inv, variables):
for dns_name in inv['hosts']:
if not dns_name in self.cache:
self.cache[dns_name] = dict()
for key, value in variables.iteritems():
if key in self.cache[dns_name]:
raise Exception("varaible {} is defined already for host={}".format(key,dns_name))
self.cache[dns_name][key] = value
self.var_names.add(key)
def push(self, my_dict, key, element):
""" Pushed an element onto an array that may not have been defined in the dict """
if key in my_dict:
my_dict[key].append(element)
else:
my_dict[key] = [element]
def to_safe(self, word):
""" Converts 'bad' characters in a string to underscores so they can be used as Ansible groups """
return re.sub("[^A-Za-z0-9\-]", "_", word)
def json_format_dict(self, data, pretty=False):
""" Converts a dict to a JSON object and dumps it as a formatted string """
if pretty:
return json.dumps(data, sort_keys=True, indent=2)
else:
return json.dumps(data)
CombinedInventory()
#!/bin/sh
find inventory/ -type f -name "*-test" -print0 \
| xargs -0 python inventory/join_inventory.py $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment