summaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rwxr-xr-xplugins/inventory/cobbler.py243
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()