diff options
Diffstat (limited to 'plugins')
-rwxr-xr-x | plugins/inventory/cobbler.py | 243 |
1 files changed, 186 insertions, 57 deletions
diff --git a/plugins/inventory/cobbler.py b/plugins/inventory/cobbler.py index e7a96ce84a..f607da2dc2 100755 --- a/plugins/inventory/cobbler.py +++ b/plugins/inventory/cobbler.py @@ -28,6 +28,11 @@ that those correspond to addresses. See http://ansible.github.com/api.html for more info Tested with Cobbler 2.0.11. + +Changelog: + - 2013-09-01 pgehres: Refactored implementation to make use of caching and to + limit the number of connections to external cobbler server for performance. + Added use of cobbler.ini file to configure settings. Tested with Cobbler 2.4.0 """ # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> @@ -50,86 +55,210 @@ Tested with Cobbler 2.0.11. ###################################################################### -import sys +import argparse +import ConfigParser +import os +import re +from time import time import xmlrpclib -import shlex try: import json -except: +except ImportError: import simplejson as json # NOTE -- this file assumes Ansible is being accessed FROM the cobbler # server, so it does not attempt to login with a username and password. # this will be addressed in a future version of this script. -conn = xmlrpclib.Server("http://127.0.0.1/cobbler_api", allow_none=True) -################################################### -# executed with no parameters, return the list of -# all groups and hosts +class CobblerInventory(object): + + def __init__(self): + """ Main execution path """ + self.conn = None + + self.inventory = dict() # A list of groups and the hosts in that group + self.cache = dict() # Details about hosts in the inventory + + # Read settings and parse CLI arguments + self.read_settings() + self.parse_cli_args() + + # Cache + if self.args.refresh_cache: + self.update_cache() + elif not self.is_cache_valid(): + self.update_cache() + else: + self.load_inventory_from_cache() + self.load_cache_from_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 _connect(self): + if not self.conn: + self.conn = xmlrpclib.Server(self.cobbler_host, allow_none=True) + + def is_cache_valid(self): + """ Determines if the cache files have expired, or if it is still valid """ + + if os.path.isfile(self.cache_path_cache): + mod_time = os.path.getmtime(self.cache_path_cache) + current_time = time() + if (mod_time + self.cache_max_age) > current_time: + if os.path.isfile(self.cache_path_inventory): + return True + + return False + + def read_settings(self): + """ Reads the settings from the cobbler.ini file """ + + config = ConfigParser.SafeConfigParser() + config.read(os.path.dirname(os.path.realpath(__file__)) + '/cobbler.ini') + + self.cobbler_host = config.get('cobbler', 'host') + + # Cache related + cache_path = config.get('cobbler', 'cache_path') + self.cache_path_cache = cache_path + "/ansible-cobbler.cache" + self.cache_path_inventory = cache_path + "/ansible-cobbler.index" + self.cache_max_age = config.getint('cobbler', 'cache_max_age') + + 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('--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() + + def update_cache(self): + """ Make calls to cobbler and save the output in a cache """ + + self._connect() + self.groups = dict() + self.hosts = dict() + + data = self.conn.get_systems() + + for host in data: + # Get the FQDN for the host and add it to the right groups + dns_name = None + ksmeta = None + interfaces = host['interfaces'] + for (iname, ivalue) in interfaces.iteritems(): + if ivalue['management']: + this_dns_name = ivalue.get('dns_name', None) + if this_dns_name is not None and this_dns_name is not "": + dns_name = this_dns_name + + if dns_name is None: + continue + + status = host['status'] + profile = host['profile'] + classes = host['mgmt_classes'] + + if status not in self.inventory: + self.inventory[status] = [] + self.inventory[status].append(dns_name) + + if profile not in self.inventory: + self.inventory[profile] = [] + self.inventory[profile].append(dns_name) + + for cls in classes: + if cls not in self.inventory: + self.inventory[cls] = [] + self.inventory[cls].append(dns_name) + + # Since we already have all of the data for the host, update the host details as well + + # The old way was ksmeta only -- provide backwards compatibility + + self.cache[dns_name] = dict() + if "ks_meta" in host: + for key, value in host["ks_meta"].iteritems(): + self.cache[dns_name][key] = value + + self.write_to_cache(self.cache, self.cache_path_cache) + self.write_to_cache(self.inventory, self.cache_path_inventory) + + def get_host_info(self): + """ Get variables about a specific host """ + + if not self.cache or len(self.cache) == 0: + # Need to load index from cache + self.load_cache_from_cache() + + if not self.args.host in self.cache: + # try updating the cache + self.update_cache() -if len(sys.argv) == 2 and (sys.argv[1] == '--list'): + if not self.args.host in self.cache: + # host might not exist anymore + return self.json_format_dict({}, True) - systems = conn.get_item_names('system') - groups = { 'ungrouped' : [] } + return self.json_format_dict(self.cache[self.args.host], True) - for system in systems: + def push(self, my_dict, key, element): + """ Pushed an element onto an array that may not have been defined in the dict """ - data = conn.get_blended_data(None, system) + if key in my_dict: + my_dict[key].append(element) + else: + my_dict[key] = [element] - dns_name = None - interfaces = data['interfaces'] - for (iname, ivalue) in interfaces.iteritems(): - this_dns_name = ivalue.get('dns_name', None) - if this_dns_name is not None: - dns_name = this_dns_name + def load_inventory_from_cache(self): + """ Reads the index from the cache file sets self.index """ - if dns_name is None: - continue + cache = open(self.cache_path_inventory, 'r') + json_inventory = cache.read() + self.inventory = json.loads(json_inventory) - classes = data['mgmt_classes'] - for cls in classes: - if cls not in groups: - groups[cls] = [] - # hostname is not really what we want to insert, really insert the - # first DNS name but no further DNS names - groups[cls].append(dns_name) + def load_cache_from_cache(self): + """ Reads the cache from the cache file sets self.cache """ - # handle hosts without mgmt_classes - if not classes: - groups['ungrouped'].append(dns_name) + cache = open(self.cache_path_cache, 'r') + json_cache = cache.read() + self.cache = json.loads(json_cache) - print json.dumps(groups) - sys.exit(0) + def write_to_cache(self, data, filename): + """ Writes data in JSON format to a file """ -##################################################### -# executed with a hostname as a parameter, return the -# variables for that host + json_data = self.json_format_dict(data, True) + cache = open(filename, 'w') + cache.write(json_data) + cache.close() -elif len(sys.argv) == 3 and (sys.argv[1] == '--host'): + def to_safe(self, word): + """ Converts 'bad' characters in a string to underscores so they can be used as Ansible groups """ - # look up the system record for the given DNS name - result = conn.find_system_by_dns_name(sys.argv[2]) - system = result.get('name', None) - data = {} - if system is None: - print json.dumps({}) - sys.exit(1) - data = conn.get_system_for_koan(system) + return re.sub("[^A-Za-z0-9\-]", "_", word) - # return the ksmeta data for that system - metadata = data['ks_meta'] - tokens = shlex.split(metadata) - results = {} - for t in tokens: - if t.find("=") != -1: - (k,v) = t.split("=",1) - results[k]=v - print json.dumps(results) - sys.exit(0) + def json_format_dict(self, data, pretty=False): + """ Converts a dict to a JSON object and dumps it as a formatted string """ -else: + if pretty: + return json.dumps(data, sort_keys=True, indent=2) + else: + return json.dumps(data) - print "usage: --list ..OR.. --host <hostname>" - sys.exit(1) +CobblerInventory() |