mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
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 against Cobbler 2.4.0
This commit is contained in:
parent
a3e3334547
commit
084dc38fbe
1 changed files with 186 additions and 57 deletions
|
@ -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):
|
||||
|
||||
if len(sys.argv) == 2 and (sys.argv[1] == '--list'):
|
||||
def __init__(self):
|
||||
""" Main execution path """
|
||||
self.conn = None
|
||||
|
||||
systems = conn.get_item_names('system')
|
||||
groups = { 'ungrouped' : [] }
|
||||
self.inventory = dict() # A list of groups and the hosts in that group
|
||||
self.cache = dict() # Details about hosts in the inventory
|
||||
|
||||
for system in systems:
|
||||
# Read settings and parse CLI arguments
|
||||
self.read_settings()
|
||||
self.parse_cli_args()
|
||||
|
||||
data = conn.get_blended_data(None, system)
|
||||
# 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()
|
||||
|
||||
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
|
||||
data_to_print = ""
|
||||
|
||||
if dns_name is None:
|
||||
continue
|
||||
# Data to print
|
||||
if self.args.host:
|
||||
data_to_print = self.get_host_info()
|
||||
|
||||
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)
|
||||
elif self.args.list:
|
||||
# Display list of instances for inventory
|
||||
data_to_print = self.json_format_dict(self.inventory, True)
|
||||
|
||||
# handle hosts without mgmt_classes
|
||||
if not classes:
|
||||
groups['ungrouped'].append(dns_name)
|
||||
else: # default action with no options
|
||||
data_to_print = self.json_format_dict(self.inventory, True)
|
||||
|
||||
print json.dumps(groups)
|
||||
sys.exit(0)
|
||||
print data_to_print
|
||||
|
||||
#####################################################
|
||||
# executed with a hostname as a parameter, return the
|
||||
# variables for that host
|
||||
def _connect(self):
|
||||
if not self.conn:
|
||||
self.conn = xmlrpclib.Server(self.cobbler_host, allow_none=True)
|
||||
|
||||
elif len(sys.argv) == 3 and (sys.argv[1] == '--host'):
|
||||
def is_cache_valid(self):
|
||||
""" Determines if the cache files have expired, or if it is still valid """
|
||||
|
||||
# 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)
|
||||
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 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)
|
||||
return False
|
||||
|
||||
else:
|
||||
def read_settings(self):
|
||||
""" Reads the settings from the cobbler.ini file """
|
||||
|
||||
print "usage: --list ..OR.. --host <hostname>"
|
||||
sys.exit(1)
|
||||
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 not self.args.host in self.cache:
|
||||
# host might not exist anymore
|
||||
return self.json_format_dict({}, True)
|
||||
|
||||
return self.json_format_dict(self.cache[self.args.host], True)
|
||||
|
||||
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 load_inventory_from_cache(self):
|
||||
""" Reads the index from the cache file sets self.index """
|
||||
|
||||
cache = open(self.cache_path_inventory, 'r')
|
||||
json_inventory = cache.read()
|
||||
self.inventory = json.loads(json_inventory)
|
||||
|
||||
def load_cache_from_cache(self):
|
||||
""" Reads the cache from the cache file sets self.cache """
|
||||
|
||||
cache = open(self.cache_path_cache, 'r')
|
||||
json_cache = cache.read()
|
||||
self.cache = json.loads(json_cache)
|
||||
|
||||
def write_to_cache(self, data, filename):
|
||||
""" Writes data in JSON format to a file """
|
||||
|
||||
json_data = self.json_format_dict(data, True)
|
||||
cache = open(filename, 'w')
|
||||
cache.write(json_data)
|
||||
cache.close()
|
||||
|
||||
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)
|
||||
|
||||
CobblerInventory()
|
||||
|
|
Loading…
Reference in a new issue