Commit bbe19f8e by Brian Coca

Merge pull request #10212 from cchurch/vmware_inventory_improvements

VMware inventory script updates from Tower.
parents a76a6ee7 48a0b6fb
# 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'): from psphere.client import Client
dpath = os.path.expanduser(config.get('cache', 'dir')) from psphere.errors import ObjectNotFoundError
try: from psphere.managedobjects import HostSystem, VirtualMachine, ManagedObject, Network
if not os.path.exists(dpath): from suds.sudsobject import Object as SudsObject
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
def cache_available(cache_item, config):
''' checks if we have a 'fresh' cache available for item requested '''
if config.has_option('cache', 'dir'):
dpath = os.path.expanduser(config.get('cache', 'dir'))
try:
existing = os.stat('/'.join([dpath,cache_item]))
except:
# cache doesn't exist or isn't accessible
return False
if config.has_option('cache', 'max_age'):
maxage = config.get('cache', 'max_age')
fileage = int( time.time() - existing.st_mtime )
if (maxage > fileage):
return True
return False class VMwareInventory(object):
def get_host_info(host): def __init__(self, guests_only=None):
''' Get variables about a specific host ''' 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
hostinfo = { # Retrieve only guest VMs, or include host systems?
'vmware_name' : host.name, if guests_only is not None:
} self.guests_only = guests_only
for k in host.capability.__dict__.keys(): 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('_'): if k.startswith('_'):
continue 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: try:
hostinfo['vmware_' + k] = str(host.capability[k]) obj_unicode = unicode(getattr(obj, 'name'))
except: 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 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:
host_info['ipAddress'] = host.config.network.vnic[0].spec.ip.ipAddress
except Exception, e:
print >> sys.stderr, e
host_info = self._flatten_dict(host_info, prefix)
if ('%s_ipAddress' % prefix) in host_info:
host_info['ansible_ssh_host'] = host_info['%s_ipAddress' % prefix]
return host_info
def _get_vm_info(self, vm, prefix='vmware'):
'''
Return a flattened dict with info about the given virtual machine.
'''
vm_info = {
'name': vm.name,
}
for attr in ('datastore', 'network'):
try:
value = getattr(vm, attr)
vm_info['%ss' % attr] = self._get_obj_info(value, depth=0)
except AttributeError:
vm_info['%ss' % attr] = []
try:
vm_info['resourcePool'] = self._get_obj_info(vm.resourcePool, depth=0)
except AttributeError:
vm_info['resourcePool'] = ''
try:
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__'
return hostinfo inv = self._get_cache(cache_name, None)
if inv is not None:
return inv
def get_inventory(client, config):
''' Reads the inventory from cache or vmware api '''
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) 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)
return json.dumps(inv) # Loop through all VMs on physical host.
def get_single_host(client, config, hostname):
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)
if __name__ == '__main__': # Group by resource pool.
vm_resourcePool = vm_info.get('vmware_resourcePool', None)
inventory = {} if vm_resourcePool:
hostname = None self._add_child(inv, vm_group, 'resource_pools')
self._add_child(inv, 'resource_pools', vm_resourcePool)
if len(sys.argv) > 1: self._add_host(inv, vm_resourcePool, vm.name)
if sys.argv[1] == "--host":
hostname = sys.argv[2] # Group by datastore.
for vm_datastore in vm_info.get('vmware_datastores', []):
# Read config self._add_child(inv, vm_group, 'datastores')
config = ConfigParser.SafeConfigParser() self._add_child(inv, 'datastores', vm_datastore)
me = os.path.abspath(sys.argv[0]).rstrip('.py') self._add_host(inv, vm_datastore, vm.name)
for configfilename in [me + '.ini', 'vmware.ini']:
if os.path.exists(configfilename): # Group by network.
config.read(configfilename) for vm_network in vm_info.get('vmware_networks', []):
break 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
mename = os.path.basename(me).upper() def get_host(self, hostname):
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'))) Read info about a specific host or VM from cache or VMware API.
password = os.getenv('VMWARE_' + mename + '_PASSWORD',os.getenv('VMWARE_PASSWORD', config.get('auth','password'))) '''
inv = self._get_cache(hostname, None)
if inv is not None:
return inv
if not self.guests_only:
try: try:
client = Client( host,user,password ) host = HostSystem.get(self.client, name=hostname)
except Exception, e: inv = self._get_host_info(host)
client = None except ObjectNotFoundError:
#print >> STDERR "Unable to login (only cache available): %s", str(e) pass
# Actually do the work if inv is None:
if hostname is None: try:
inventory = get_inventory(client, config) 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: else:
inventory = get_single_host(client, config, hostname) inventory = vmware_inventory.get_inventory(options.meta_hostvars)
# Return to ansible json_kwargs = {}
print inventory if options.pretty:
json_kwargs.update({'indent': 4, 'sort_keys': True})
json.dump(inventory, sys.stdout, **json_kwargs)
if __name__ == '__main__':
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