chef_inventory.py (170 lines of code) (raw):
#!/usr/bin/env python
from __future__ import print_function
import argparse
import json
import chef
import os
import sys
import re
from time import time
import ConfigParser
try:
import json
except ImportError:
import simplejson as json
class ChefInventory:
def __init__(self):
self.parser = self._create_parser()
self.chef_server_url = ""
self.chef_server_ssl_verify = True
self.client_key = ""
self.client_name = ""
self.cache_max_age = 3600
self.cache_path = os.path.join(os.path.expanduser('~'),'.ansible-chef.cache')
self.read_settings()
self.api = chef.autoconfigure()
if self.chef_server_url and self.client_key and self.client_name:
print("Using chef ini values", file=sys.stderr)
self.api = chef.ChefAPI(url=self.chef_server_url,
key=self.client_key,
client=self.client_name,
ssl_verify=self.chef_server_ssl_verify)
else:
pemfile = os.environ.get('CHEF_PEMFILE')
username = os.environ.get('CHEF_USER')
chef_server_url = os.environ.get('CHEF_SERVER_URL')
chef_server_ssl_verify = os.environ.get('CHEF_SERVER_SSL_VERIFY', True) in [True, 'True', 'true', 1, '1']
if not self.api:
if pemfile is None or username is None or chef_server_url is None:
print("Set CHEF_PEMFILE, CHEF_USER and CHEF_SERVER_URL environment vars. They might be located under ~/.chef/knife_local.rb or ~/.chef/knife.rb")
exit(0)
self.api=chef.ChefAPI(url=chef_server_url,
key=pemfile,
client=username,
ssl_verify=chef_server_ssl_verify)
if not self.api:
print("Could not find chef configuration", file=sys.stderr)
sys.exit(1)
def read_settings(self):
config = ConfigParser.SafeConfigParser()
chef_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'chef.ini')
chef_ini_path = os.environ.get('CHEF_INI_PATH', chef_default_ini_path)
config.read(chef_ini_path)
if config.has_option('chef', 'cache_path'):
cache_dir = os.path.expanduser(config.get('chef', 'cache_path'))
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
self.cache_path = cache_dir + "/ansible-chef.cache"
if config.has_option('chef', 'cache_max_age'):
self.cache_max_age = int(config.get('chef', 'cache_max_age'))
if config.has_option('chef', 'chef_server_url'):
self.chef_server_url = config.get('chef', 'chef_server_url')
if config.has_option('chef', 'client_key'):
self.client_key = os.path.expanduser(config.get('chef', 'client_key'))
if config.has_option('chef', 'client_name'):
self.client_name = config.get('chef', 'client_name')
if config.has_option('chef', 'chef_server_ssl_verify'):
self.chef_server_ssl_verify = config.getboolean('chef', 'chef_server_ssl_verify')
def refresh_cache(self):
print("REFRESHING CACHE - COULD TAKE A WHILE", file=sys.stderr)
with self.api:
data = {}
nodes = chef.Search("node")
for n in nodes:
data[n["name"]] = n
self.write_cache(data)
def _create_parser(self):
parser = argparse.ArgumentParser(
description=u'Chef Server Dynamic Inventory for Ansible.'
)
parser.add_argument(u'--list', action=u'store_true',
help=u'List all nodes.')
parser.add_argument(u'--host', help=u'Retrieve variable information.')
parser.add_argument(u'--refresh-cache', action=u'store_true', help=u'Refresh the cache')
return parser
def read_cache(self):
cache = open(self.cache_path, 'r')
data = json.loads(cache.read())
return data
def write_cache(self, data):
json_data = self.json_format_dict(data, True)
cache = open(self.cache_path, 'w')
cache.write(json_data)
cache.close()
def is_cache_valid(self):
if os.path.isfile(self.cache_path):
mod_time = os.path.getmtime(self.cache_path)
current_time = time()
if (mod_time + self.cache_max_age) > current_time:
return True
return False
def json_format_dict(self, data, pretty=False):
if pretty:
return json.dumps(data, sort_keys=True, indent=2)
else:
return json.dumps(data)
def to_safe(self, word):
word = re.sub(":{2,}",':', word)
word = re.sub("[^A-Za-z0-9\-]", "_", word)
return word
def check_key(self, dic, attr):
if attr in dic:
return dic[attr]
else:
return ['none']
def ip_for_node(self, node):
return node["automatic"]["ipaddress"]
def list_nodes(self):
groups = {}
hostvars = {}
data = self.read_cache()
for name, node in data.iteritems():
# make sure node is configured/working
if ( "ipaddress" in node["automatic"].keys() ):
if name not in hostvars:
hostvars[name] = {}
hostvars[name]['ansible_ssh_host'] = self.ip_for_node(node)
if name.startswith('fe-'):
hostvars[name]['ansible_ssh_port'] = '2222'
hostvars[name]['ansible_python_interpreter'] = '/usr/bin/python3'
else:
continue
# create a list of environments
environment = "chef_environment_%s" % self.to_safe(node["chef_environment"])
if environment not in groups:
groups[environment] = []
groups[environment].append(name)
if ('roles' in node["automatic"]) and ( node["automatic"]["roles"] is not None):
for r in self.check_key(node["automatic"], "roles"):
role = "role_%s" % self.to_safe(r)
if role not in groups:
groups[role] = []
groups[role].append(node["automatic"]["fqdn"])
if 'expanded_run_list' in node["automatic"]:
for r in self.check_key(node['automatic'], 'expanded_run_list'):
recipe = "recipe_%s" % self.to_safe(r)
if recipe not in groups: groups[recipe] = []
groups[recipe].append(self.ip_for_node(node))
if node["normal"]["tags"] is not None:
for tag in self.check_key(node['normal'], 'tags'):
tag = "tag_%s" % self.to_safe(tag)
if tag not in groups: groups[tag] = []
groups[tag].append(self.ip_for_node(node))
# remove any duplicates
groups = {key : list(set(items)) for (key, items) in groups.iteritems() }
meta = { "_meta" : { "hostvars" : hostvars } }
groups.update(meta)
print(self.json_format_dict(groups, pretty=True))
def execute(self):
args = self.parser.parse_args()
if args.refresh_cache:
self.refresh_cache()
elif not self.is_cache_valid():
self.refresh_cache()
if args.list:
self.list_nodes()
elif args.host:
print(json.dumps(dict())) ## not implemented. May cause issues for old versions of ansible?
else:
self.parser.parse_args(['-h'])
def main():
ci = ChefInventory()
ci.execute()
if __name__ == '__main__':
main()