Created
April 12, 2016 21:42
-
-
Save techzilla/37479f70a83a65ab1f69af0bb2d17430 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# (c) 2016, J. M. Becker <[email protected]> | |
# GPL V3 | |
###################################################################### | |
''' | |
VMware Inventory Script | |
======================= | |
''' | |
import os | |
import sys | |
import argparse | |
import re | |
import ssl | |
import atexit | |
from time import time | |
import ConfigParser | |
from six import iteritems, string_types | |
from pyVim.connect import SmartConnect, Disconnect | |
from pyVmomi import vim, vmodl | |
try: | |
import json | |
except ImportError: | |
import simplejson as json | |
class VMwareInventory(object): | |
def __init__(self): | |
''' Main execution path ''' | |
# Inventory grouped by instance IDs, tags, security groups, regions, | |
# and availability zones | |
self.inventory = {} | |
# Index of hostname (address) to instance ID | |
self.index = {} | |
# Read settings and parse CLI arguments | |
self.parse_cli_args() | |
self.read_settings() | |
# Cache | |
if self.args.refresh_cache: | |
self.do_api_calls_update_cache() | |
elif not self.is_cache_valid(): | |
self.do_api_calls_update_cache() | |
# Data to print | |
if self.args.host: | |
data_to_print = self.get_host_info() | |
elif self.args.list: | |
# Display list of instances for inventory | |
if len(self.inventory) == 0: | |
data_to_print = self.get_inventory_from_cache() | |
else: | |
data_to_print = self.json_format_dict(self.inventory, True) | |
print(data_to_print) | |
def connect_to_vcenter(self): | |
global service_instance | |
service_instance = SmartConnect(host=self.host, | |
user=self.user, | |
pwd=self.password, | |
port=443) | |
# content = self.service_instance.RetrieveContent() | |
# | |
atexit.register(Disconnect, service_instance) | |
global _get_content | |
def _get_content(obj_type, property_list=None): | |
# Create an object view | |
obj_view = service_instance.content.viewManager.CreateContainerView(service_instance.content.rootFolder, [obj_type], True) | |
# Create traversal spec to determine the path for collection | |
traversal_spec = vmodl.query.PropertyCollector.TraversalSpec( | |
name='traverseEntities', | |
path='view', | |
skip=False, | |
type=vim.view.ContainerView | |
) | |
# Create property spec to determine properties to be retrieved | |
property_spec = vmodl.query.PropertyCollector.PropertySpec( | |
type=obj_type, | |
all=True if not property_list else False, | |
pathSet=property_list | |
) | |
# Create object spec to navigate content | |
obj_spec = vmodl.query.PropertyCollector.ObjectSpec( | |
obj=obj_view, | |
skip=True, | |
selectSet=[traversal_spec] | |
) | |
# Create a filter spec and specify object, property spec in it | |
filter_spec = vmodl.query.PropertyCollector.FilterSpec( | |
objectSet=[obj_spec], | |
propSet=[property_spec], | |
reportMissingObjectsInResults=False | |
) | |
# Retrieve the contents | |
content = service_instance.content.propertyCollector.RetrieveContents([filter_spec]) | |
# Destroy the object view | |
obj_view.Destroy() | |
return content | |
global _get_mors_with_properties | |
def _get_mors_with_properties(obj_type, property_list=None): | |
''' | |
Returns list containing properties and managed object references for the managed object | |
''' | |
# Get all the content | |
content = _get_content(obj_type, property_list) | |
object_list = [] | |
for object in content: | |
properties = {} | |
for property in object.propSet: | |
properties[property.name] = property.val | |
properties['object'] = object.obj | |
object_list.append(properties) | |
return object_list | |
def _get_mor_by_property(obj_type, property_value, property_name='name'): | |
''' | |
Returns the first managed object reference having the specified property value | |
''' | |
# Get list of all managed object references with specified property | |
object_list = _get_mors_with_properties(obj_type, [property_name]) | |
for object in object_list: | |
if object[property_name] == property_value: | |
return object['object'] | |
return None | |
def is_cache_valid(self): | |
''' Determines if the cache files have expired, or if it is still valid ''' | |
if os.path.isfile(self.cache_path_cache): | |
mod_time = os.path.getmtime(self.cache_path_cache) | |
current_time = time() | |
if (mod_time + self.cache_max_age) > current_time: | |
if os.path.isfile(self.cache_path_index): | |
return True | |
return False | |
def read_settings(self): | |
''' Reads the settings from the vmware.ini file ''' | |
config = ConfigParser.SafeConfigParser() | |
vmware_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'vmware.ini') | |
vmware_ini_path = os.environ.get('VMWARE_INI_PATH', vmware_default_ini_path) | |
config.read(vmware_ini_path) | |
if not config.has_section('driver'): | |
raise ValueError('vmware.ini file must contain a [driver] section') | |
if config.has_option('driver', 'host'): | |
self.host = config.get('driver','host') | |
else: | |
raise ValueError('vmware.ini does not have a provider defined') | |
if config.has_option('driver', 'user'): | |
self.user = config.get('driver','user') | |
else: | |
raise ValueError('vmware.ini does not have a key defined') | |
if config.has_option('driver', 'password'): | |
self.password = config.get('driver','password') | |
else: | |
raise ValueError('vmware.ini does not have a secret defined') | |
## | |
ssl._create_default_https_context = ssl._create_unverified_context | |
try: | |
self.connect_to_vcenter() | |
except vmodl.MethodFault as error: | |
print "Caught vmodl fault : " + error.msg | |
return -1 | |
# Cache related | |
cache_path = config.get('cache', 'cache_path') | |
self.cache_path_cache = cache_path + "/ansible-vmware.cache" | |
self.cache_path_index = cache_path + "/ansible-vmware.index" | |
self.cache_max_age = config.getint('cache', 'cache_max_age') | |
def parse_cli_args(self): | |
''' | |
Command line argument processing | |
''' | |
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on vmware supported providers') | |
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('--insecure', action='store_true', default=False, | |
help='Ignore Unverified SSL') | |
parser.add_argument('--refresh-cache', action='store_true', default=False, | |
help='Force refresh of cache by making API requests to vmware supported providers (default: False - use cache files)') | |
self.args = parser.parse_args() | |
def do_api_calls_update_cache(self): | |
''' | |
Do API calls to a location, and save data in cache files | |
''' | |
self.get_nodes() | |
self.write_to_cache(self.inventory, self.cache_path_cache) | |
self.write_to_cache(self.index, self.cache_path_index) | |
def get_nodes(self): | |
''' | |
Gets the list of all nodes | |
''' | |
# ret = {} | |
vm_properties = ["name","config.guestId","runtime.powerState"] | |
vm_list = _get_mors_with_properties(vim.VirtualMachine, vm_properties) | |
for vm in vm_list: | |
if vm["runtime.powerState"] == 'poweredOn': | |
if vm["config.guestId"]: | |
self.add_node(vm) | |
# for node in self.conn.list_nodes(): | |
# self.add_node(node) | |
def get_node(self, node_id): | |
''' | |
Gets details about a specific node | |
''' | |
# return [node for node in self.conn.list_nodes() if node.id == node_id][0] | |
def add_node(self, node): | |
''' | |
Adds a node to the inventory and index, as long as it is | |
addressable | |
''' | |
dest = node['name'] | |
# Add to index | |
self.index[dest] = node['name'] | |
# Inventory: Group by instance ID (always a group of 1) | |
self.inventory[node['name']] = [dest] | |
# Inventory: Group by region | |
self.push(self.inventory, node['config.guestId'], dest) | |
''' | |
# Inventory: Group by availability zone | |
self.push(self.inventory, node.placement, dest) | |
# Inventory: Group by instance type | |
self.push(self.inventory, self.to_safe('type_' + node.instance_type), dest) | |
''' | |
# Inventory: Group by key pair | |
# if node.extra['key_name']: | |
# self.push(self.inventory, self.to_safe('key_' + node.extra['key_name']), dest) | |
# | |
# # Inventory: Group by security group, quick thing to handle single sg | |
# if node.extra['security_group']: | |
# self.push(self.inventory, self.to_safe('sg_' + node.extra['security_group'][0]), dest) | |
# | |
# # Inventory: Group by tag | |
# if node.extra['tags']: | |
# for tagkey in node.extra['tags'].keys(): | |
# self.push(self.inventory, self.to_safe('tag_' + tagkey + '_' + node.extra['tags'][tagkey]), dest) | |
def get_host_info(self): | |
''' | |
Get variables about a specific host | |
''' | |
# if len(self.index) == 0: | |
# # Need to load index from cache | |
# self.load_index_from_cache() | |
# | |
# if not self.args.host in self.index: | |
# # try updating the cache | |
# self.do_api_calls_update_cache() | |
# if not self.args.host in self.index: | |
# # host migh not exist anymore | |
# return self.json_format_dict({}, True) | |
# | |
# node_id = self.index[self.args.host] | |
# | |
# node = self.get_node(node_id) | |
# instance_vars = {} | |
# for key in vars(instance): | |
# value = getattr(instance, key) | |
# key = self.to_safe('ec2_' + key) | |
# | |
# # Handle complex types | |
# if isinstance(value, (int, bool)): | |
# instance_vars[key] = value | |
# elif isinstance(value, string_types): | |
# instance_vars[key] = value.strip() | |
# elif value is None: | |
# instance_vars[key] = '' | |
# elif key == 'ec2_region': | |
# instance_vars[key] = value.name | |
# elif key == 'ec2_tags': | |
# for k, v in iteritems(value): | |
# key = self.to_safe('ec2_tag_' + k) | |
# instance_vars[key] = v | |
# elif key == 'ec2_groups': | |
# group_ids = [] | |
# group_names = [] | |
# for group in value: | |
# group_ids.append(group.id) | |
# group_names.append(group.name) | |
# instance_vars["ec2_security_group_ids"] = ','.join(group_ids) | |
# instance_vars["ec2_security_group_names"] = ','.join(group_names) | |
# else: | |
# pass | |
# # TODO Product codes if someone finds them useful | |
# #print(key) | |
# #print(type(value)) | |
# #print(value) | |
# | |
# return self.json_format_dict(instance_vars, 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 get_inventory_from_cache(self): | |
''' | |
Reads the inventory from the cache file and returns it as a JSON | |
object | |
''' | |
cache = open(self.cache_path_cache, 'r') | |
json_inventory = cache.read() | |
return json_inventory | |
def load_index_from_cache(self): | |
''' | |
Reads the index from the cache file sets self.index | |
''' | |
cache = open(self.cache_path_index, 'r') | |
json_index = cache.read() | |
self.index = json.loads(json_index) | |
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) | |
def main(): | |
VMwareInventory() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment