Commit 48a0b6fb by Chris Church

VMware inventory script updates from Tower.

parent c31831f0
# Ansible vmware external inventory script settings # Ansible VMware external inventory script settings
#
[defaults] [defaults]
# If true (the default), return only guest VMs. If false, also return host
# systems in the results.
guests_only = True guests_only = True
#vm_group =
#hw_group =
[cache] # Specify an alternate group name for guest VMs. If not defined, defaults to
max_age = 3600 # the basename of the inventory script + "_vm", e.g. "vmware_vm".
dir = ~/.cache/ansible #vm_group = vm_group_name
# Specify an alternate group name for host systems when guests_only=false.
# If not defined, defaults to the basename of the inventory script + "_hw",
# e.g. "vmware_hw".
#hw_group = hw_group_name
# Specify the number of seconds to use the inventory cache before it is
# considered stale. If not defined, defaults to 0 seconds.
#cache_max_age = 3600
# Specify the directory used for storing the inventory cache. If not defined,
# caching will be disabled.
#cache_dir = ~/.cache/ansible
[auth] [auth]
# Specify hostname or IP address of vCenter/ESXi server. A port may be
# included with the hostname, e.g.: vcenter.example.com:8443. This setting
# may also be defined via the VMWARE_HOST environment variable.
host = vcenter.example.com host = vcenter.example.com
# Specify a username to access the vCenter host. This setting may also be
# defined with the VMWARE_USER environment variable.
user = ihasaccess user = ihasaccess
# Specify a password to access the vCenter host. This setting may also be
# defined with the VMWARE_PASSWORD environment variable.
password = ssshverysecret password = ssshverysecret
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' '''
VMWARE external inventory script VMware Inventory Script
================================= =======================
shamelessly copied from existing inventory scripts. Retrieve information about virtual machines from a vCenter server or
standalone ESX host. When `group_by=false` (in the INI file), host systems
This script and it's ini can be used more than once, are also returned in addition to VMs.
i.e. vmware.py/vmware_colo.ini vmware_idf.py/vmware_idf.ini This script will attempt to read configuration from an INI file with the same
(script can be link) base filename if present, or `vmware.ini` if not. It is possible to create
symlinks to the inventory script to support multiple configurations, e.g.:
so if you don't have clustered vcenter but multiple esx machines or
just diff clusters you can have an inventory per each and automatically * `vmware.py` (this script)
group hosts based on file name or specify a group in the ini. * `vmware.ini` (default configuration, will be read by `vmware.py`)
* `vmware_test.py` (symlink to `vmware.py`)
You can also use <SCRIPT_NAME>_HOST|USER|PASSWORD environment variables * `vmware_test.ini` (test configuration, will be read by `vmware_test.py`)
to override the ini. * `vmware_other.py` (symlink to `vmware.py`, will read `vmware.ini` since no
`vmware_other.ini` exists)
The path to an INI file may also be specified via the `VMWARE_INI` environment
variable, in which case the filename matching rules above will not apply.
Host and authentication parameters may be specified via the `VMWARE_HOST`,
`VMWARE_USER` and `VMWARE_PASSWORD` environment variables; these options will
take precedence over options present in the INI file. An INI file is not
required if these options are specified using environment variables.
''' '''
import collections
import json
import logging
import optparse
import os import os
import sys import sys
import time import time
import ConfigParser import ConfigParser
from psphere.client import Client
from psphere.managedobjects import HostSystem
# Disable logging message trigged by pSphere/suds.
try: try:
import json from logging import NullHandler
except ImportError: except ImportError:
import simplejson as json from logging import Handler
class NullHandler(Handler):
def emit(self, record):
def save_cache(cache_item, data, config): pass
''' saves item to cache ''' logging.getLogger('psphere').addHandler(NullHandler())
logging.getLogger('suds').addHandler(NullHandler())
if config.has_option('cache', 'dir'):
dpath = os.path.expanduser(config.get('cache', 'dir'))
try:
if not os.path.exists(dpath):
os.makedirs(dpath)
if os.path.isdir(dpath):
cache = open('/'.join([dpath,cache_item]), 'w')
cache.write(json.dumps(data))
cache.close()
except IOError, e:
pass # not really sure what to do here
def get_cache(cache_item, config):
''' returns cached item '''
inv = {}
if config.has_option('cache', 'dir'):
dpath = os.path.expanduser(config.get('cache', 'dir'))
try:
cache = open('/'.join([dpath,cache_item]), 'r')
inv = json.loads(cache.read())
cache.close()
except IOError, e:
pass # not really sure what to do here
return inv from psphere.client import Client
from psphere.errors import ObjectNotFoundError
from psphere.managedobjects import HostSystem, VirtualMachine, ManagedObject, Network
from suds.sudsobject import Object as SudsObject
def cache_available(cache_item, config):
''' checks if we have a 'fresh' cache available for item requested '''
if config.has_option('cache', 'dir'): class VMwareInventory(object):
dpath = os.path.expanduser(config.get('cache', 'dir'))
def __init__(self, guests_only=None):
self.config = ConfigParser.SafeConfigParser()
if os.environ.get('VMWARE_INI', ''):
config_files = [os.environ['VMWARE_INI']]
else:
config_files = [os.path.abspath(sys.argv[0]).rstrip('.py') + '.ini', 'vmware.ini']
for config_file in config_files:
if os.path.exists(config_file):
self.config.read(config_file)
break
# Retrieve only guest VMs, or include host systems?
if guests_only is not None:
self.guests_only = guests_only
elif self.config.has_option('defaults', 'guests_only'):
self.guests_only = self.config.getboolean('defaults', 'guests_only')
else:
self.guests_only = True
# Read authentication information from VMware environment variables
# (if set), otherwise from INI file.
auth_host = os.environ.get('VMWARE_HOST')
if not auth_host and self.config.has_option('auth', 'host'):
auth_host = self.config.get('auth', 'host')
auth_user = os.environ.get('VMWARE_USER')
if not auth_user and self.config.has_option('auth', 'user'):
auth_user = self.config.get('auth', 'user')
auth_password = os.environ.get('VMWARE_PASSWORD')
if not auth_password and self.config.has_option('auth', 'password'):
auth_password = self.config.get('auth', 'password')
# Create the VMware client connection.
self.client = Client(auth_host, auth_user, auth_password)
def _put_cache(self, name, value):
'''
Saves the value to cache with the name given.
'''
if self.config.has_option('defaults', 'cache_dir'):
cache_dir = self.config.get('defaults', 'cache_dir')
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
cache_file = os.path.join(cache_dir, name)
with open(cache_file, 'w') as cache:
json.dump(value, cache)
def _get_cache(self, name, default=None):
'''
Retrieves the value from cache for the given name.
'''
if self.config.has_option('defaults', 'cache_dir'):
cache_dir = self.config.get('defaults', 'cache_dir')
cache_file = os.path.join(cache_dir, name)
if os.path.exists(cache_file):
if self.config.has_option('defaults', 'cache_max_age'):
cache_max_age = self.config.getint('defaults', 'cache_max_age')
else:
cache_max_age = 0
cache_stat = os.stat(cache_file)
if (cache_stat.st_mtime + cache_max_age) < time.time():
with open(cache_file) as cache:
return json.load(cache)
return default
def _flatten_dict(self, d, parent_key='', sep='_'):
'''
Flatten nested dicts by combining keys with a separator. Lists with
only string items are included as is; any other lists are discarded.
'''
items = []
for k, v in d.items():
if k.startswith('_'):
continue
new_key = parent_key + sep + k if parent_key else k
if isinstance(v, collections.MutableMapping):
items.extend(self._flatten_dict(v, new_key, sep).items())
elif isinstance(v, (list, tuple)):
if all([isinstance(x, basestring) for x in v]):
items.append((new_key, v))
else:
items.append((new_key, v))
return dict(items)
def _get_obj_info(self, obj, depth=99, seen=None):
'''
Recursively build a data structure for the given pSphere object (depth
only applies to ManagedObject instances).
'''
seen = seen or set()
if isinstance(obj, ManagedObject):
try:
obj_unicode = unicode(getattr(obj, 'name'))
except AttributeError:
obj_unicode = ()
if obj in seen:
return obj_unicode
seen.add(obj)
if depth <= 0:
return obj_unicode
d = {}
for attr in dir(obj):
if attr.startswith('_'):
continue
try:
val = getattr(obj, attr)
obj_info = self._get_obj_info(val, depth - 1, seen)
if obj_info != ():
d[attr] = obj_info
except Exception, e:
pass
return d
elif isinstance(obj, SudsObject):
d = {}
for key, val in iter(obj):
obj_info = self._get_obj_info(val, depth, seen)
if obj_info != ():
d[key] = obj_info
return d
elif isinstance(obj, (list, tuple)):
l = []
for val in iter(obj):
obj_info = self._get_obj_info(val, depth, seen)
if obj_info != ():
l.append(obj_info)
return l
elif isinstance(obj, (type(None), bool, int, long, float, basestring)):
return obj
else:
return ()
def _get_host_info(self, host, prefix='vmware'):
'''
Return a flattened dict with info about the given host system.
'''
host_info = {
'name': host.name,
}
for attr in ('datastore', 'network', 'vm'):
try:
value = getattr(host, attr)
host_info['%ss' % attr] = self._get_obj_info(value, depth=0)
except AttributeError:
host_info['%ss' % attr] = []
for k, v in self._get_obj_info(host.summary, depth=0).items():
if isinstance(v, collections.MutableMapping):
for k2, v2 in v.items():
host_info[k2] = v2
elif k != 'host':
host_info[k] = v
try: try:
existing = os.stat('/'.join([dpath,cache_item])) host_info['ipAddress'] = host.config.network.vnic[0].spec.ip.ipAddress
except: except Exception, e:
# cache doesn't exist or isn't accessible print >> sys.stderr, e
return False host_info = self._flatten_dict(host_info, prefix)
if ('%s_ipAddress' % prefix) in host_info:
if config.has_option('cache', 'max_age'): host_info['ansible_ssh_host'] = host_info['%s_ipAddress' % prefix]
maxage = config.get('cache', 'max_age') return host_info
fileage = int( time.time() - existing.st_mtime )
if (maxage > fileage): def _get_vm_info(self, vm, prefix='vmware'):
return True '''
Return a flattened dict with info about the given virtual machine.
return False '''
vm_info = {
def get_host_info(host): 'name': vm.name,
''' Get variables about a specific host ''' }
for attr in ('datastore', 'network'):
hostinfo = { try:
'vmware_name' : host.name, value = getattr(vm, attr)
} vm_info['%ss' % attr] = self._get_obj_info(value, depth=0)
for k in host.capability.__dict__.keys(): except AttributeError:
if k.startswith('_'): vm_info['%ss' % attr] = []
continue
try: try:
hostinfo['vmware_' + k] = str(host.capability[k]) vm_info['resourcePool'] = self._get_obj_info(vm.resourcePool, depth=0)
except: except AttributeError:
continue vm_info['resourcePool'] = ''
try:
return hostinfo vm_info['guestState'] = vm.guest.guestState
except AttributeError:
vm_info['guestState'] = ''
for k, v in self._get_obj_info(vm.summary, depth=0).items():
if isinstance(v, collections.MutableMapping):
for k2, v2 in v.items():
if k2 == 'host':
k2 = 'hostSystem'
vm_info[k2] = v2
elif k != 'vm':
vm_info[k] = v
vm_info = self._flatten_dict(vm_info, prefix)
if ('%s_ipAddress' % prefix) in vm_info:
vm_info['ansible_ssh_host'] = vm_info['%s_ipAddress' % prefix]
return vm_info
def _add_host(self, inv, parent_group, host_name):
'''
Add the host to the parent group in the given inventory.
'''
p_group = inv.setdefault(parent_group, [])
if isinstance(p_group, dict):
group_hosts = p_group.setdefault('hosts', [])
else:
group_hosts = p_group
if host_name not in group_hosts:
group_hosts.append(host_name)
def _add_child(self, inv, parent_group, child_group):
'''
Add a child group to a parent group in the given inventory.
'''
if parent_group != 'all':
p_group = inv.setdefault(parent_group, {})
if not isinstance(p_group, dict):
inv[parent_group] = {'hosts': p_group}
p_group = inv[parent_group]
group_children = p_group.setdefault('children', [])
if child_group not in group_children:
group_children.append(child_group)
inv.setdefault(child_group, [])
def get_inventory(self, meta_hostvars=True):
'''
Reads the inventory from cache or VMware API via pSphere.
'''
# Use different cache names for guests only vs. all hosts.
if self.guests_only:
cache_name = '__inventory_guests__'
else:
cache_name = '__inventory_all__'
def get_inventory(client, config): inv = self._get_cache(cache_name, None)
''' Reads the inventory from cache or vmware api ''' if inv is not None:
return inv
inv = {} inv = {'all': {'hosts': []}}
if meta_hostvars:
inv['_meta'] = {'hostvars': {}}
if cache_available('inventory', config):
inv = get_cache('inventory',config)
elif client:
inv= { 'all': {'hosts': []}, '_meta': { 'hostvars': {} } }
default_group = os.path.basename(sys.argv[0]).rstrip('.py') default_group = os.path.basename(sys.argv[0]).rstrip('.py')
if config.has_option('defaults', 'guests_only'): if not self.guests_only:
guests_only = config.get('defaults', 'guests_only') if self.config.has_option('defaults', 'hw_group'):
else: hw_group = self.config.get('defaults', 'hw_group')
guests_only = True
if not guests_only:
if config.has_option('defaults','hw_group'):
hw_group = config.get('defaults','hw_group')
else: else:
hw_group = default_group + '_hw' hw_group = default_group + '_hw'
inv[hw_group] = []
if config.has_option('defaults','vm_group'): if self.config.has_option('defaults', 'vm_group'):
vm_group = config.get('defaults','vm_group') vm_group = self.config.get('defaults', 'vm_group')
else: else:
vm_group = default_group + '_vm' vm_group = default_group + '_vm'
inv[vm_group] = []
# Loop through physical hosts: # Loop through physical hosts:
hosts = HostSystem.all(client) for host in HostSystem.all(self.client):
for host in hosts:
if not guests_only:
inv['all']['hosts'].append(host.name)
inv[hw_group].append(host.name)
inv['_meta']['hostvars'][host.name] = get_host_info(host)
save_cache(vm.name, inv['_meta']['hostvars'][host.name], config)
for vm in host.vm:
inv['all']['hosts'].append(vm.name)
inv[vm_group].append(vm.name)
inv['_meta']['hostvars'][vm.name] = get_host_info(vm)
save_cache(vm.name, inv['_meta']['hostvars'][vm.name], config)
save_cache('inventory', inv, config)
return json.dumps(inv) if not self.guests_only:
self._add_host(inv, 'all', host.name)
self._add_host(inv, hw_group, host.name)
host_info = self._get_host_info(host)
if meta_hostvars:
inv['_meta']['hostvars'][host.name] = host_info
self._put_cache(host.name, host_info)
def get_single_host(client, config, hostname): # Loop through all VMs on physical host.
inv = {}
if cache_available(hostname, config):
inv = get_cache(hostname,config)
elif client:
hosts = HostSystem.all(client) #TODO: figure out single host getter
for host in hosts:
if hostname == host.name:
inv = get_host_info(host)
break
for vm in host.vm: for vm in host.vm:
if hostname == vm.name: self._add_host(inv, 'all', vm.name)
inv = get_host_info(vm) self._add_host(inv, vm_group, vm.name)
break vm_info = self._get_vm_info(vm)
save_cache(hostname,inv,config) if meta_hostvars:
inv['_meta']['hostvars'][vm.name] = vm_info
return json.dumps(inv) self._put_cache(vm.name, vm_info)
# Group by resource pool.
vm_resourcePool = vm_info.get('vmware_resourcePool', None)
if vm_resourcePool:
self._add_child(inv, vm_group, 'resource_pools')
self._add_child(inv, 'resource_pools', vm_resourcePool)
self._add_host(inv, vm_resourcePool, vm.name)
# Group by datastore.
for vm_datastore in vm_info.get('vmware_datastores', []):
self._add_child(inv, vm_group, 'datastores')
self._add_child(inv, 'datastores', vm_datastore)
self._add_host(inv, vm_datastore, vm.name)
# Group by network.
for vm_network in vm_info.get('vmware_networks', []):
self._add_child(inv, vm_group, 'networks')
self._add_child(inv, 'networks', vm_network)
self._add_host(inv, vm_network, vm.name)
# Group by guest OS.
vm_guestId = vm_info.get('vmware_guestId', None)
if vm_guestId:
self._add_child(inv, vm_group, 'guests')
self._add_child(inv, 'guests', vm_guestId)
self._add_host(inv, vm_guestId, vm.name)
# Group all VM templates.
vm_template = vm_info.get('vmware_template', False)
if vm_template:
self._add_child(inv, vm_group, 'templates')
self._add_host(inv, 'templates', vm.name)
self._put_cache(cache_name, inv)
return inv
def get_host(self, hostname):
'''
Read info about a specific host or VM from cache or VMware API.
'''
inv = self._get_cache(hostname, None)
if inv is not None:
return inv
if not self.guests_only:
try:
host = HostSystem.get(self.client, name=hostname)
inv = self._get_host_info(host)
except ObjectNotFoundError:
pass
if inv is None:
try:
vm = VirtualMachine.get(self.client, name=hostname)
inv = self._get_vm_info(vm)
except ObjectNotFoundError:
pass
if inv is not None:
self._put_cache(hostname, inv)
return inv or {}
def main():
parser = optparse.OptionParser()
parser.add_option('--list', action='store_true', dest='list',
default=False, help='Output inventory groups and hosts')
parser.add_option('--host', dest='host', default=None, metavar='HOST',
help='Output variables only for the given hostname')
# Additional options for use when running the script standalone, but never
# used by Ansible.
parser.add_option('--pretty', action='store_true', dest='pretty',
default=False, help='Output nicely-formatted JSON')
parser.add_option('--include-host-systems', action='store_true',
dest='include_host_systems', default=False,
help='Include host systems in addition to VMs')
parser.add_option('--no-meta-hostvars', action='store_false',
dest='meta_hostvars', default=True,
help='Exclude [\'_meta\'][\'hostvars\'] with --list')
options, args = parser.parse_args()
if options.include_host_systems:
vmware_inventory = VMwareInventory(guests_only=False)
else:
vmware_inventory = VMwareInventory()
if options.host is not None:
inventory = vmware_inventory.get_host(options.host)
else:
inventory = vmware_inventory.get_inventory(options.meta_hostvars)
if __name__ == '__main__': json_kwargs = {}
if options.pretty:
json_kwargs.update({'indent': 4, 'sort_keys': True})
json.dump(inventory, sys.stdout, **json_kwargs)
inventory = {}
hostname = None
if len(sys.argv) > 1:
if sys.argv[1] == "--host":
hostname = sys.argv[2]
# Read config
config = ConfigParser.SafeConfigParser()
me = os.path.abspath(sys.argv[0]).rstrip('.py')
for configfilename in [me + '.ini', 'vmware.ini']:
if os.path.exists(configfilename):
config.read(configfilename)
break
mename = os.path.basename(me).upper()
host = os.getenv('VMWARE_' + mename + '_HOST',os.getenv('VMWARE_HOST', config.get('auth','host')))
user = os.getenv('VMWARE_' + mename + '_USER', os.getenv('VMWARE_USER', config.get('auth','user')))
password = os.getenv('VMWARE_' + mename + '_PASSWORD',os.getenv('VMWARE_PASSWORD', config.get('auth','password')))
try:
client = Client( host,user,password )
except Exception, e:
client = None
#print >> STDERR "Unable to login (only cache available): %s", str(e)
# Actually do the work
if hostname is None:
inventory = get_inventory(client, config)
else:
inventory = get_single_host(client, config, hostname)
# Return to ansible if __name__ == '__main__':
print inventory main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment