diff options
Diffstat (limited to 'contrib')
-rw-r--r-- | contrib/inventory/vmware.ini | 50 | ||||
-rwxr-xr-x | contrib/inventory/vmware.py | 472 | ||||
-rw-r--r-- | contrib/inventory/vmware_inventory.ini | 127 | ||||
-rwxr-xr-x | contrib/inventory/vmware_inventory.py | 793 |
4 files changed, 0 insertions, 1442 deletions
diff --git a/contrib/inventory/vmware.ini b/contrib/inventory/vmware.ini deleted file mode 100644 index 93de5d67b4..0000000000 --- a/contrib/inventory/vmware.ini +++ /dev/null @@ -1,50 +0,0 @@ -# Ansible VMware external inventory script settings - -[defaults] - -# If true (the default), return only guest VMs. If false, also return host -# systems in the results. -guests_only = True - -# Specify an alternate group name for guest VMs. If not defined, defaults to -# the basename of the inventory script + "_vm", e.g. "vmware_vm". -#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 - -# Specify a prefix filter. Any VMs with names beginning with this string will -# not be returned. -# prefix_filter = test_ - -# Specify a cluster filter list (colon delimited). Only clusters matching by -# name will be scanned for virtualmachines -#clusters = cluster1,cluster2 - -[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 - -# Specify a username to access the vCenter host. This setting may also be -# defined with the VMWARE_USER environment variable. -user = ihasaccess - -# Specify a password to access the vCenter host. This setting may also be -# defined with the VMWARE_PASSWORD environment variable. -password = ssshverysecret - -# Force SSL certificate checking by default or ignore self-signed certs. -#sslcheck=True diff --git a/contrib/inventory/vmware.py b/contrib/inventory/vmware.py deleted file mode 100755 index 483fd42318..0000000000 --- a/contrib/inventory/vmware.py +++ /dev/null @@ -1,472 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -''' -VMware Inventory Script -======================= - -Retrieve information about virtual machines from a vCenter server or -standalone ESX host. When `group_by=false` (in the INI file), host systems -are also returned in addition to VMs. - -This script will attempt to read configuration from an INI file with the same -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.: - -* `vmware.py` (this script) -* `vmware.ini` (default configuration, will be read by `vmware.py`) -* `vmware_test.py` (symlink to `vmware.py`) -* `vmware_test.ini` (test configuration, will be read by `vmware_test.py`) -* `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. -''' - -from __future__ import print_function - -import json -import logging -import optparse -import os -import ssl -import sys -import time - -from ansible.module_utils.common._collections_compat import MutableMapping -from ansible.module_utils.six import integer_types, text_type, string_types -from ansible.module_utils.six.moves import configparser - -# Disable logging message trigged by pSphere/suds. -try: - from logging import NullHandler -except ImportError: - from logging import Handler - - class NullHandler(Handler): - def emit(self, record): - pass - -logging.getLogger('psphere').addHandler(NullHandler()) -logging.getLogger('suds').addHandler(NullHandler()) - -from psphere.client import Client -from psphere.errors import ObjectNotFoundError -from psphere.managedobjects import HostSystem, VirtualMachine, ManagedObject, Network, ClusterComputeResource -from suds.sudsobject import Object as SudsObject - - -class VMwareInventory(object): - - 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') - sslcheck = os.environ.get('VMWARE_SSLCHECK') - if not sslcheck and self.config.has_option('auth', 'sslcheck'): - sslcheck = self.config.get('auth', 'sslcheck') - if not sslcheck: - sslcheck = True - else: - if sslcheck.lower() in ['no', 'false']: - sslcheck = False - else: - sslcheck = True - - # Limit the clusters being scanned - self.filter_clusters = os.environ.get('VMWARE_CLUSTERS') - if not self.filter_clusters and self.config.has_option('defaults', 'clusters'): - self.filter_clusters = self.config.get('defaults', 'clusters') - if self.filter_clusters: - self.filter_clusters = [x.strip() for x in self.filter_clusters.split(',') if x.strip()] - - # Override certificate checks - if not sslcheck: - if hasattr(ssl, '_create_unverified_context'): - ssl._create_default_https_context = ssl._create_unverified_context - - # 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 = os.path.expanduser(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, MutableMapping): - items.extend(self._flatten_dict(v, new_key, sep).items()) - elif isinstance(v, (list, tuple)): - if all([isinstance(x, string_types) 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 = text_type(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 as 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, float) + string_types + integer_types): - 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, 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 as e: - print(e, file=sys.stderr) - 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, 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__' - - inv = self._get_cache(cache_name, None) - if inv is not None: - return inv - - inv = {'all': {'hosts': []}} - if meta_hostvars: - inv['_meta'] = {'hostvars': {}} - - default_group = os.path.basename(sys.argv[0]).rstrip('.py') - - if not self.guests_only: - if self.config.has_option('defaults', 'hw_group'): - hw_group = self.config.get('defaults', 'hw_group') - else: - hw_group = default_group + '_hw' - - if self.config.has_option('defaults', 'vm_group'): - vm_group = self.config.get('defaults', 'vm_group') - else: - vm_group = default_group + '_vm' - - if self.config.has_option('defaults', 'prefix_filter'): - prefix_filter = self.config.get('defaults', 'prefix_filter') - else: - prefix_filter = None - - if self.filter_clusters: - # Loop through clusters and find hosts: - hosts = [] - for cluster in ClusterComputeResource.all(self.client): - if cluster.name in self.filter_clusters: - for host in cluster.host: - hosts.append(host) - else: - # Get list of all physical hosts - hosts = HostSystem.all(self.client) - - # Loop through physical hosts: - for host in hosts: - - 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) - - # Loop through all VMs on physical host. - for vm in host.vm: - if prefix_filter: - if vm.name.startswith(prefix_filter): - continue - self._add_host(inv, 'all', vm.name) - self._add_host(inv, vm_group, vm.name) - vm_info = self._get_vm_info(vm) - if meta_hostvars: - inv['_meta']['hostvars'][vm.name] = vm_info - 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) - - json_kwargs = {} - if options.pretty: - json_kwargs.update({'indent': 4, 'sort_keys': True}) - json.dump(inventory, sys.stdout, **json_kwargs) - - -if __name__ == '__main__': - main() diff --git a/contrib/inventory/vmware_inventory.ini b/contrib/inventory/vmware_inventory.ini deleted file mode 100644 index f94570f891..0000000000 --- a/contrib/inventory/vmware_inventory.ini +++ /dev/null @@ -1,127 +0,0 @@ -# Ansible VMware external inventory script settings - -[vmware] - -# The resolvable hostname or ip address of the vsphere -server=vcenter - -# The port for the vsphere API -#port=443 - -# The username with access to the vsphere API. This setting -# may also be defined via the VMWARE_USERNAME environment variable. -username=administrator@vsphere.local - -# The password for the vsphere API. This setting -# may also be defined via the VMWARE_PASSWORD environment variable. -password=vmware - -# Verify the server's SSL certificate -#validate_certs = True - -# 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_path = ~/.cache/ansible - - -# Max object level refers to the level of recursion the script will delve into -# the objects returned from pyvomi to find serializable facts. The default -# level of 0 is sufficient for most tasks and will be the most performant. -# Beware that the recursion can exceed python's limit (causing traceback), -# cause sluggish script performance and return huge blobs of facts. -# If you do not know what you are doing, leave this set to 1. -#max_object_level=1 - - -# Lower the keynames for facts to make addressing them easier. -#lower_var_keys=True - - -# Don't retrieve and process some VMware attribute keys -# Default values permit to sanitize inventory meta and to improve a little bit -# performance by removing non-common group attributes. -#skip_keys = declaredalarmstate,disabledmethod,dynamicproperty,dynamictype,environmentbrowser,managedby,parent,childtype,resourceconfig - - -# Host alias for objects in the inventory. VMware allows duplicate VM names -# so they can not be considered unique. Use this setting to alter the alias -# returned for the hosts. Any atributes for the guest can be used to build -# this alias. The default combines the config name and the config uuid and -# expects that the ansible_host will be set by the host_pattern. -#alias_pattern={{ config.name + '_' + config.uuid }} - - -# Host pattern is the value set for ansible_host and ansible_ssh_host, which -# needs to be a hostname or ipaddress the ansible controlhost can reach. -#host_pattern={{ guest.ipaddress }} - - -# Host filters are a comma separated list of jinja patterns to remove -# non-matching hosts from the final result. -# EXAMPLES: -# host_filters={{ config.guestid == 'rhel7_64Guest' }} -# host_filters={{ config.cpuhotremoveenabled != False }},{{ runtime.maxmemoryusage >= 512 }} -# host_filters={{ config.cpuhotremoveenabled != False }},{{ runtime.maxmemoryusage >= 512 }} -# host_filters={{ runtime.powerstate == "poweredOn" }} -# host_filters={{ guest.gueststate == "notRunning" }} -# The default value is powerstate of virtual machine equal to "poweredOn". (Changed in version 2.5) -# Runtime state does not require to have vmware tools installed as compared to "guest.gueststate" -#host_filters={{ runtime.powerstate == "poweredOn" }} - - - -# Groupby patterns enable the user to create groups via any possible jinja -# expression. The resulting value will the groupname and the host will be added -# to that group. Be careful to not make expressions that simply return True/False -# because those values will become the literal group name. The patterns can be -# comma delimited to create as many groups as necessary -#groupby_patterns={{ guest.guestid }},{{ 'templates' if config.template else 'guests'}} - -# Group by custom fields will use VMware custom fields to generate hostgroups -# based on {{ custom_field_group_prefix }} + field_name + _ + field_value -# Set groupby_custom_field to True will enable this feature -# If custom field value is comma separated, multiple groups are created. -# Warning: This required max_object_level to be set to 2 or greater. -#groupby_custom_field = False - -# You can customize prefix used by custom field hostgroups generation here. -# vmware_tag_ prefix is the default and consistent with ec2_tag_ -#custom_field_group_prefix = vmware_tag_ - -# You can blacklist custom fields so that they are not included in the -# groupby_custom_field option. This is useful when you have custom fields that -# have values that are unique to individual hosts. Timestamps for example. -# The groupby_custom_field_excludes option should be a comma separated list of custom -# field keys to be blacklisted. -#groupby_custom_field_excludes=<custom_field_1>,<custom_field_2>,<custom_field_3> - -# The script attempts to recurse into virtualmachine objects and serialize -# all available data. The serialization is comprehensive but slow. If the -# vcenter environment is large and the desired properties are known, create -# a 'properties' section in this config and make an arbitrary list of -# key=value settings where the value is a path to a specific property. If -# If this feature is enabled, be sure to fetch every property that is used -# in the jinja expressions defined above. For performance tuning, reduce -# the number of properties to the smallest amount possible and limit the -# use of properties that are not direct attributes of vim.VirtualMachine -#[properties] -#prop01=name -#prop02=config.cpuHotAddEnabled -#prop03=config.cpuHotRemoveEnabled -#prop04=config.instanceUuid -#prop05=config.hardware.numCPU -#prop06=config.template -#prop07=config.name -#prop08=guest.hostName -#prop09=guest.ipAddress -#prop10=guest.guestId -#prop11=guest.guestState -#prop12=runtime.maxMemoryUsage -# In order to populate `customValue` (virtual machine's custom attributes) inside hostvars, -# uncomment following property. Please see - https://github.com/ansible/ansible/issues/41395 -#prop13=customValue diff --git a/contrib/inventory/vmware_inventory.py b/contrib/inventory/vmware_inventory.py deleted file mode 100755 index 0271110c96..0000000000 --- a/contrib/inventory/vmware_inventory.py +++ /dev/null @@ -1,793 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright (C): 2017, Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# Requirements -# - pyvmomi >= 6.0.0.2016.4 - -# TODO: -# * more jq examples -# * optional folder hierarchy - -""" -$ jq '._meta.hostvars[].config' data.json | head -{ - "alternateguestname": "", - "instanceuuid": "5035a5cd-b8e8-d717-e133-2d383eb0d675", - "memoryhotaddenabled": false, - "guestfullname": "Red Hat Enterprise Linux 7 (64-bit)", - "changeversion": "2016-05-16T18:43:14.977925Z", - "uuid": "4235fc97-5ddb-7a17-193b-9a3ac97dc7b4", - "cpuhotremoveenabled": false, - "vpmcenabled": false, - "firmware": "bios", -""" - -from __future__ import print_function - -import atexit -import datetime -import itertools -import json -import os -import re -import ssl -import sys -import uuid -from time import time - -from jinja2 import Environment - -from ansible.module_utils.six import integer_types, PY3 -from ansible.module_utils.six.moves import configparser - -try: - import argparse -except ImportError: - sys.exit('Error: This inventory script required "argparse" python module. Please install it or upgrade to python-2.7') - -try: - from pyVmomi import vim, vmodl - from pyVim.connect import SmartConnect, Disconnect -except ImportError: - sys.exit("ERROR: This inventory script required 'pyVmomi' Python module, it was not able to load it") - - -def regex_match(s, pattern): - '''Custom filter for regex matching''' - reg = re.compile(pattern) - if reg.match(s): - return True - else: - return False - - -def select_chain_match(inlist, key, pattern): - '''Get a key from a list of dicts, squash values to a single list, then filter''' - outlist = [x[key] for x in inlist] - outlist = list(itertools.chain(*outlist)) - outlist = [x for x in outlist if regex_match(x, pattern)] - return outlist - - -class VMwareMissingHostException(Exception): - pass - - -class VMWareInventory(object): - __name__ = 'VMWareInventory' - - guest_props = False - instances = [] - debug = False - load_dumpfile = None - write_dumpfile = None - maxlevel = 1 - lowerkeys = True - config = None - cache_max_age = None - cache_path_cache = None - cache_path_index = None - cache_dir = None - server = None - port = None - username = None - password = None - validate_certs = True - host_filters = [] - skip_keys = [] - groupby_patterns = [] - groupby_custom_field_excludes = [] - - safe_types = [bool, str, float, None] + list(integer_types) - iter_types = [dict, list] - - bad_types = ['Array', 'disabledMethod', 'declaredAlarmState'] - - vimTableMaxDepth = { - "vim.HostSystem": 2, - "vim.VirtualMachine": 2, - } - - custom_fields = {} - - # use jinja environments to allow for custom filters - env = Environment() - env.filters['regex_match'] = regex_match - env.filters['select_chain_match'] = select_chain_match - - # translation table for attributes to fetch for known vim types - - vimTable = { - vim.Datastore: ['_moId', 'name'], - vim.ResourcePool: ['_moId', 'name'], - vim.HostSystem: ['_moId', 'name'], - } - - @staticmethod - def _empty_inventory(): - return {"_meta": {"hostvars": {}}} - - def __init__(self, load=True): - self.inventory = VMWareInventory._empty_inventory() - - if load: - # Read settings and parse CLI arguments - self.parse_cli_args() - self.read_settings() - - # Check the cache - cache_valid = self.is_cache_valid() - - # Handle Cache - if self.args.refresh_cache or not cache_valid: - self.do_api_calls_update_cache() - else: - self.debugl('loading inventory from cache') - self.inventory = self.get_inventory_from_cache() - - def debugl(self, text): - if self.args.debug: - try: - text = str(text) - except UnicodeEncodeError: - text = text.encode('utf-8') - print('%s %s' % (datetime.datetime.now(), text)) - - def show(self): - # Data to print - self.debugl('dumping results') - data_to_print = None - if self.args.host: - data_to_print = self.get_host_info(self.args.host) - elif self.args.list: - # Display list of instances for inventory - data_to_print = self.inventory - return json.dumps(data_to_print, indent=2) - - def is_cache_valid(self): - ''' Determines if the cache files have expired, or if it is still valid ''' - - valid = False - - 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: - valid = True - - return valid - - def do_api_calls_update_cache(self): - ''' Get instances and cache the data ''' - self.inventory = self.instances_to_inventory(self.get_instances()) - self.write_to_cache(self.inventory) - - def write_to_cache(self, data): - ''' Dump inventory to json file ''' - with open(self.cache_path_cache, 'w') as f: - f.write(json.dumps(data, indent=2)) - - def get_inventory_from_cache(self): - ''' Read in jsonified inventory ''' - - jdata = None - with open(self.cache_path_cache, 'r') as f: - jdata = f.read() - return json.loads(jdata) - - def read_settings(self): - ''' Reads the settings from the vmware_inventory.ini file ''' - - scriptbasename = __file__ - scriptbasename = os.path.basename(scriptbasename) - scriptbasename = scriptbasename.replace('.py', '') - - defaults = {'vmware': { - 'server': '', - 'port': 443, - 'username': '', - 'password': '', - 'validate_certs': True, - 'ini_path': os.path.join(os.path.dirname(__file__), '%s.ini' % scriptbasename), - 'cache_name': 'ansible-vmware', - 'cache_path': '~/.ansible/tmp', - 'cache_max_age': 3600, - 'max_object_level': 1, - 'skip_keys': 'declaredalarmstate,' - 'disabledmethod,' - 'dynamicproperty,' - 'dynamictype,' - 'environmentbrowser,' - 'managedby,' - 'parent,' - 'childtype,' - 'resourceconfig', - 'alias_pattern': '{{ config.name + "_" + config.uuid }}', - 'host_pattern': '{{ guest.ipaddress }}', - 'host_filters': '{{ runtime.powerstate == "poweredOn" }}', - 'groupby_patterns': '{{ guest.guestid }},{{ "templates" if config.template else "guests"}}', - 'lower_var_keys': True, - 'custom_field_group_prefix': 'vmware_tag_', - 'groupby_custom_field_excludes': '', - 'groupby_custom_field': False} - } - - if PY3: - config = configparser.ConfigParser() - else: - config = configparser.SafeConfigParser() - - # where is the config? - vmware_ini_path = os.environ.get('VMWARE_INI_PATH', defaults['vmware']['ini_path']) - vmware_ini_path = os.path.expanduser(os.path.expandvars(vmware_ini_path)) - config.read(vmware_ini_path) - - if 'vmware' not in config.sections(): - config.add_section('vmware') - - # apply defaults - for k, v in defaults['vmware'].items(): - if not config.has_option('vmware', k): - config.set('vmware', k, str(v)) - - # where is the cache? - self.cache_dir = os.path.expanduser(config.get('vmware', 'cache_path')) - if self.cache_dir and not os.path.exists(self.cache_dir): - os.makedirs(self.cache_dir) - - # set the cache filename and max age - cache_name = config.get('vmware', 'cache_name') - self.cache_path_cache = self.cache_dir + "/%s.cache" % cache_name - self.debugl('cache path is %s' % self.cache_path_cache) - self.cache_max_age = int(config.getint('vmware', 'cache_max_age')) - - # mark the connection info - self.server = os.environ.get('VMWARE_SERVER', config.get('vmware', 'server')) - self.debugl('server is %s' % self.server) - self.port = int(os.environ.get('VMWARE_PORT', config.get('vmware', 'port'))) - self.username = os.environ.get('VMWARE_USERNAME', config.get('vmware', 'username')) - self.debugl('username is %s' % self.username) - self.password = os.environ.get('VMWARE_PASSWORD', config.get('vmware', 'password', raw=True)) - self.validate_certs = os.environ.get('VMWARE_VALIDATE_CERTS', config.get('vmware', 'validate_certs')) - if self.validate_certs in ['no', 'false', 'False', False]: - self.validate_certs = False - - self.debugl('cert validation is %s' % self.validate_certs) - - # behavior control - self.maxlevel = int(config.get('vmware', 'max_object_level')) - self.debugl('max object level is %s' % self.maxlevel) - self.lowerkeys = config.get('vmware', 'lower_var_keys') - if type(self.lowerkeys) != bool: - if str(self.lowerkeys).lower() in ['yes', 'true', '1']: - self.lowerkeys = True - else: - self.lowerkeys = False - self.debugl('lower keys is %s' % self.lowerkeys) - self.skip_keys = list(config.get('vmware', 'skip_keys').split(',')) - self.debugl('skip keys is %s' % self.skip_keys) - temp_host_filters = list(config.get('vmware', 'host_filters').split('}},')) - for host_filter in temp_host_filters: - host_filter = host_filter.rstrip() - if host_filter != "": - if not host_filter.endswith("}}"): - host_filter += "}}" - self.host_filters.append(host_filter) - self.debugl('host filters are %s' % self.host_filters) - - temp_groupby_patterns = list(config.get('vmware', 'groupby_patterns').split('}},')) - for groupby_pattern in temp_groupby_patterns: - groupby_pattern = groupby_pattern.rstrip() - if groupby_pattern != "": - if not groupby_pattern.endswith("}}"): - groupby_pattern += "}}" - self.groupby_patterns.append(groupby_pattern) - self.debugl('groupby patterns are %s' % self.groupby_patterns) - temp_groupby_custom_field_excludes = config.get('vmware', 'groupby_custom_field_excludes') - self.groupby_custom_field_excludes = [x.strip('"') for x in [y.strip("'") for y in temp_groupby_custom_field_excludes.split(",")]] - self.debugl('groupby exclude strings are %s' % self.groupby_custom_field_excludes) - - # Special feature to disable the brute force serialization of the - # virtual machine objects. The key name for these properties does not - # matter because the values are just items for a larger list. - if config.has_section('properties'): - self.guest_props = [] - for prop in config.items('properties'): - self.guest_props.append(prop[1]) - - # save the config - self.config = config - - def parse_cli_args(self): - ''' Command line argument processing ''' - - parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on PyVmomi') - parser.add_argument('--debug', action='store_true', default=False, - help='show debug info') - 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 VSphere (default: False - use cache files)') - parser.add_argument('--max-instances', default=None, type=int, - help='maximum number of instances to retrieve') - self.args = parser.parse_args() - - def get_instances(self): - ''' Get a list of vm instances with pyvmomi ''' - kwargs = {'host': self.server, - 'user': self.username, - 'pwd': self.password, - 'port': int(self.port)} - - if self.validate_certs and hasattr(ssl, 'SSLContext'): - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - context.verify_mode = ssl.CERT_REQUIRED - context.check_hostname = True - kwargs['sslContext'] = context - elif self.validate_certs and not hasattr(ssl, 'SSLContext'): - sys.exit('pyVim does not support changing verification mode with python < 2.7.9. Either update ' - 'python or use validate_certs=false.') - elif not self.validate_certs and hasattr(ssl, 'SSLContext'): - context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) - context.verify_mode = ssl.CERT_NONE - context.check_hostname = False - kwargs['sslContext'] = context - elif not self.validate_certs and not hasattr(ssl, 'SSLContext'): - # Python 2.7.9 < or RHEL/CentOS 7.4 < - pass - - return self._get_instances(kwargs) - - def _get_instances(self, inkwargs): - ''' Make API calls ''' - instances = [] - si = None - try: - si = SmartConnect(**inkwargs) - except ssl.SSLError as connection_error: - if '[SSL: CERTIFICATE_VERIFY_FAILED]' in str(connection_error) and self.validate_certs: - sys.exit("Unable to connect to ESXi server due to %s, " - "please specify validate_certs=False and try again" % connection_error) - - except Exception as exc: - self.debugl("Unable to connect to ESXi server due to %s" % exc) - sys.exit("Unable to connect to ESXi server due to %s" % exc) - - self.debugl('retrieving all instances') - if not si: - sys.exit("Could not connect to the specified host using specified " - "username and password") - atexit.register(Disconnect, si) - content = si.RetrieveContent() - - # Create a search container for virtualmachines - self.debugl('creating containerview for virtualmachines') - container = content.rootFolder - viewType = [vim.VirtualMachine] - recursive = True - containerView = content.viewManager.CreateContainerView(container, viewType, recursive) - children = containerView.view - for child in children: - # If requested, limit the total number of instances - if self.args.max_instances: - if len(instances) >= self.args.max_instances: - break - instances.append(child) - self.debugl("%s total instances in container view" % len(instances)) - - if self.args.host: - instances = [x for x in instances if x.name == self.args.host] - - instance_tuples = [] - for instance in instances: - if self.guest_props: - ifacts = self.facts_from_proplist(instance) - else: - ifacts = self.facts_from_vobj(instance) - instance_tuples.append((instance, ifacts)) - self.debugl('facts collected for all instances') - - try: - cfm = content.customFieldsManager - if cfm is not None and cfm.field: - for f in cfm.field: - if not f.managedObjectType or f.managedObjectType == vim.VirtualMachine: - self.custom_fields[f.key] = f.name - self.debugl('%d custom fields collected' % len(self.custom_fields)) - except vmodl.RuntimeFault as exc: - self.debugl("Unable to gather custom fields due to %s" % exc.msg) - except IndexError as exc: - self.debugl("Unable to gather custom fields due to %s" % exc) - - return instance_tuples - - def instances_to_inventory(self, instances): - ''' Convert a list of vm objects into a json compliant inventory ''' - self.debugl('re-indexing instances based on ini settings') - inventory = VMWareInventory._empty_inventory() - inventory['all'] = {} - inventory['all']['hosts'] = [] - for idx, instance in enumerate(instances): - # make a unique id for this object to avoid vmware's - # numerous uuid's which aren't all unique. - thisid = str(uuid.uuid4()) - idata = instance[1] - - # Put it in the inventory - inventory['all']['hosts'].append(thisid) - inventory['_meta']['hostvars'][thisid] = idata.copy() - inventory['_meta']['hostvars'][thisid]['ansible_uuid'] = thisid - - # Make a map of the uuid to the alias the user wants - name_mapping = self.create_template_mapping( - inventory, - self.config.get('vmware', 'alias_pattern') - ) - - # Make a map of the uuid to the ssh hostname the user wants - host_mapping = self.create_template_mapping( - inventory, - self.config.get('vmware', 'host_pattern') - ) - - # Reset the inventory keys - for k, v in name_mapping.items(): - - if not host_mapping or k not in host_mapping: - continue - - # set ansible_host (2.x) - try: - inventory['_meta']['hostvars'][k]['ansible_host'] = host_mapping[k] - # 1.9.x backwards compliance - inventory['_meta']['hostvars'][k]['ansible_ssh_host'] = host_mapping[k] - except Exception: - continue - - if k == v: - continue - - # add new key - inventory['all']['hosts'].append(v) - inventory['_meta']['hostvars'][v] = inventory['_meta']['hostvars'][k] - - # cleanup old key - inventory['all']['hosts'].remove(k) - inventory['_meta']['hostvars'].pop(k, None) - - self.debugl('pre-filtered hosts:') - for i in inventory['all']['hosts']: - self.debugl(' * %s' % i) - # Apply host filters - for hf in self.host_filters: - if not hf: - continue - self.debugl('filter: %s' % hf) - filter_map = self.create_template_mapping(inventory, hf, dtype='boolean') - for k, v in filter_map.items(): - if not v: - # delete this host - inventory['all']['hosts'].remove(k) - inventory['_meta']['hostvars'].pop(k, None) - - self.debugl('post-filter hosts:') - for i in inventory['all']['hosts']: - self.debugl(' * %s' % i) - - # Create groups - for gbp in self.groupby_patterns: - groupby_map = self.create_template_mapping(inventory, gbp) - for k, v in groupby_map.items(): - if v not in inventory: - inventory[v] = {} - inventory[v]['hosts'] = [] - if k not in inventory[v]['hosts']: - inventory[v]['hosts'].append(k) - - if self.config.get('vmware', 'groupby_custom_field'): - for k, v in inventory['_meta']['hostvars'].items(): - if 'customvalue' in v: - for tv in v['customvalue']: - newkey = None - field_name = self.custom_fields[tv['key']] if tv['key'] in self.custom_fields else tv['key'] - if field_name in self.groupby_custom_field_excludes: - continue - values = [] - keylist = map(lambda x: x.strip(), tv['value'].split(',')) - for kl in keylist: - try: - newkey = "%s%s_%s" % (self.config.get('vmware', 'custom_field_group_prefix'), str(field_name), kl) - newkey = newkey.strip() - except Exception as e: - self.debugl(e) - values.append(newkey) - for tag in values: - if not tag: - continue - if tag not in inventory: - inventory[tag] = {} - inventory[tag]['hosts'] = [] - if k not in inventory[tag]['hosts']: - inventory[tag]['hosts'].append(k) - - return inventory - - def create_template_mapping(self, inventory, pattern, dtype='string'): - ''' Return a hash of uuid to templated string from pattern ''' - mapping = {} - for k, v in inventory['_meta']['hostvars'].items(): - t = self.env.from_string(pattern) - newkey = None - try: - newkey = t.render(v) - newkey = newkey.strip() - except Exception as e: - self.debugl(e) - if not newkey: - continue - elif dtype == 'integer': - newkey = int(newkey) - elif dtype == 'boolean': - if newkey.lower() == 'false': - newkey = False - elif newkey.lower() == 'true': - newkey = True - elif dtype == 'string': - pass - mapping[k] = newkey - return mapping - - def facts_from_proplist(self, vm): - '''Get specific properties instead of serializing everything''' - - rdata = {} - for prop in self.guest_props: - self.debugl('getting %s property for %s' % (prop, vm.name)) - key = prop - if self.lowerkeys: - key = key.lower() - - if '.' not in prop: - # props without periods are direct attributes of the parent - vm_property = getattr(vm, prop) - if isinstance(vm_property, vim.CustomFieldsManager.Value.Array): - temp_vm_property = [] - for vm_prop in vm_property: - temp_vm_property.append({'key': vm_prop.key, - 'value': vm_prop.value}) - rdata[key] = temp_vm_property - else: - rdata[key] = vm_property - else: - # props with periods are subkeys of parent attributes - parts = prop.split('.') - total = len(parts) - 1 - - # pointer to the current object - val = None - # pointer to the current result key - lastref = rdata - - for idx, x in enumerate(parts): - - if isinstance(val, dict): - if x in val: - val = val.get(x) - elif x.lower() in val: - val = val.get(x.lower()) - else: - # if the val wasn't set yet, get it from the parent - if not val: - try: - val = getattr(vm, x) - except AttributeError as e: - self.debugl(e) - else: - # in a subkey, get the subprop from the previous attrib - try: - val = getattr(val, x) - except AttributeError as e: - self.debugl(e) - - # make sure it serializes - val = self._process_object_types(val) - - # lowercase keys if requested - if self.lowerkeys: - x = x.lower() - - # change the pointer or set the final value - if idx != total: - if x not in lastref: - lastref[x] = {} - lastref = lastref[x] - else: - lastref[x] = val - if self.args.debug: - self.debugl("For %s" % vm.name) - for key in list(rdata.keys()): - if isinstance(rdata[key], dict): - for ikey in list(rdata[key].keys()): - self.debugl("Property '%s.%s' has value '%s'" % (key, ikey, rdata[key][ikey])) - else: - self.debugl("Property '%s' has value '%s'" % (key, rdata[key])) - return rdata - - def facts_from_vobj(self, vobj, level=0): - ''' Traverse a VM object and return a json compliant data structure ''' - - # pyvmomi objects are not yet serializable, but may be one day ... - # https://github.com/vmware/pyvmomi/issues/21 - - # WARNING: - # Accessing an object attribute will trigger a SOAP call to the remote. - # Increasing the attributes collected or the depth of recursion greatly - # increases runtime duration and potentially memory+network utilization. - - if level == 0: - try: - self.debugl("get facts for %s" % vobj.name) - except Exception as e: - self.debugl(e) - - rdata = {} - - methods = dir(vobj) - methods = [str(x) for x in methods if not x.startswith('_')] - methods = [x for x in methods if x not in self.bad_types] - methods = [x for x in methods if not x.lower() in self.skip_keys] - methods = sorted(methods) - - for method in methods: - # Attempt to get the method, skip on fail - try: - methodToCall = getattr(vobj, method) - except Exception as e: - continue - - # Skip callable methods - if callable(methodToCall): - continue - - if self.lowerkeys: - method = method.lower() - - rdata[method] = self._process_object_types( - methodToCall, - thisvm=vobj, - inkey=method, - ) - - return rdata - - def _process_object_types(self, vobj, thisvm=None, inkey='', level=0): - ''' Serialize an object ''' - rdata = {} - - if type(vobj).__name__ in self.vimTableMaxDepth and level >= self.vimTableMaxDepth[type(vobj).__name__]: - return rdata - - if vobj is None: - rdata = None - elif type(vobj) in self.vimTable: - rdata = {} - for key in self.vimTable[type(vobj)]: - try: - rdata[key] = getattr(vobj, key) - except Exception as e: - self.debugl(e) - - elif issubclass(type(vobj), str) or isinstance(vobj, str): - if vobj.isalnum(): - rdata = vobj - else: - rdata = vobj.encode('utf-8').decode('utf-8') - elif issubclass(type(vobj), bool) or isinstance(vobj, bool): - rdata = vobj - elif issubclass(type(vobj), integer_types) or isinstance(vobj, integer_types): - rdata = vobj - elif issubclass(type(vobj), float) or isinstance(vobj, float): - rdata = vobj - elif issubclass(type(vobj), list) or issubclass(type(vobj), tuple): - rdata = [] - try: - vobj = sorted(vobj) - except Exception: - pass - - for idv, vii in enumerate(vobj): - if level + 1 <= self.maxlevel: - vid = self._process_object_types( - vii, - thisvm=thisvm, - inkey=inkey + '[' + str(idv) + ']', - level=(level + 1) - ) - - if vid: - rdata.append(vid) - - elif issubclass(type(vobj), dict): - pass - - elif issubclass(type(vobj), object): - methods = dir(vobj) - methods = [str(x) for x in methods if not x.startswith('_')] - methods = [x for x in methods if x not in self.bad_types] - methods = [x for x in methods if not inkey + '.' + x.lower() in self.skip_keys] - methods = sorted(methods) - - for method in methods: - # Attempt to get the method, skip on fail - try: - methodToCall = getattr(vobj, method) - except Exception as e: - continue - - if callable(methodToCall): - continue - - if self.lowerkeys: - method = method.lower() - if level + 1 <= self.maxlevel: - try: - rdata[method] = self._process_object_types( - methodToCall, - thisvm=thisvm, - inkey=inkey + '.' + method, - level=(level + 1) - ) - except vim.fault.NoPermission: - self.debugl("Skipping method %s (NoPermission)" % method) - else: - pass - - return rdata - - def get_host_info(self, host): - ''' Return hostvars for a single host ''' - - if host in self.inventory['_meta']['hostvars']: - return self.inventory['_meta']['hostvars'][host] - elif self.args.host and self.inventory['_meta']['hostvars']: - match = None - for k, v in self.inventory['_meta']['hostvars'].items(): - if self.inventory['_meta']['hostvars'][k]['name'] == self.args.host: - match = k - break - if match: - return self.inventory['_meta']['hostvars'][match] - else: - raise VMwareMissingHostException('%s not found' % host) - else: - raise VMwareMissingHostException('%s not found' % host) - - -if __name__ == "__main__": - # Run the script - print(VMWareInventory().show()) |