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()