#!/usr/bin/env python # Copyright 2015 IIX Inc. # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <http://www.gnu.org/licenses/>. """ ovirt external inventory script ================================= Generates inventory that Ansible can understand by making API requests to oVirt via the ovirt-engine-sdk-python library. When run against a specific host, this script returns the following variables based on the data obtained from the ovirt_sdk Node object: - ovirt_uuid - ovirt_id - ovirt_image - ovirt_machine_type - ovirt_ips - ovirt_name - ovirt_description - ovirt_status - ovirt_zone - ovirt_tags - ovirt_stats When run in --list mode, instances are grouped by the following categories: - zone: zone group name. - instance tags: An entry is created for each tag. For example, if you have two instances with a common tag called 'foo', they will both be grouped together under the 'tag_foo' name. - network name: the name of the network is appended to 'network_' (e.g. the 'default' network will result in a group named 'network_default') - running status: group name prefixed with 'status_' (e.g. status_up, status_down,..) Examples: Execute uname on all instances in the us-central1-a zone $ ansible -i ovirt.py us-central1-a -m shell -a "/bin/uname -a" Use the ovirt inventory script to print out instance specific information $ contrib/inventory/ovirt.py --host my_instance Author: Josha Inglis <jinglis@iix.net> based on the gce.py by Eric Johnson <erjohnso@google.com> Version: 0.0.1 """ USER_AGENT_PRODUCT = "Ansible-ovirt_inventory_plugin" USER_AGENT_VERSION = "v1" import sys import os import argparse import ConfigParser from collections import defaultdict try: import json except ImportError: # noinspection PyUnresolvedReferences,PyPackageRequirements import simplejson as json try: # noinspection PyUnresolvedReferences from ovirtsdk.api import API # noinspection PyUnresolvedReferences from ovirtsdk.xml import params except ImportError: print("ovirt inventory script requires ovirt-engine-sdk-python") sys.exit(1) class OVirtInventory(object): def __init__(self): # Read settings and parse CLI arguments self.args = self.parse_cli_args() self.driver = self.get_ovirt_driver() # Just display data for specific host if self.args.host: print(self.json_format_dict( self.node_to_dict(self.get_instance(self.args.host)), pretty=self.args.pretty )) sys.exit(0) # Otherwise, assume user wants all instances grouped print( self.json_format_dict( data=self.group_instances(), pretty=self.args.pretty ) ) sys.exit(0) @staticmethod def get_ovirt_driver(): """ Determine the ovirt authorization settings and return a ovirt_sdk driver. :rtype : ovirtsdk.api.API """ kwargs = {} ovirt_ini_default_path = os.path.join( os.path.dirname(os.path.realpath(__file__)), "ovirt.ini") ovirt_ini_path = os.environ.get('OVIRT_INI_PATH', ovirt_ini_default_path) # Create a ConfigParser. # This provides empty defaults to each key, so that environment # variable configuration (as opposed to INI configuration) is able # to work. config = ConfigParser.SafeConfigParser(defaults={ 'ovirt_url': '', 'ovirt_username': '', 'ovirt_password': '', 'ovirt_api_secrets': '', }) if 'ovirt' not in config.sections(): config.add_section('ovirt') config.read(ovirt_ini_path) # Attempt to get ovirt params from a configuration file, if one # exists. secrets_path = config.get('ovirt', 'ovirt_api_secrets') secrets_found = False try: # noinspection PyUnresolvedReferences,PyPackageRequirements import secrets kwargs = getattr(secrets, 'OVIRT_KEYWORD_PARAMS', {}) secrets_found = True except ImportError: pass if not secrets_found and secrets_path: if not secrets_path.endswith('secrets.py'): err = "Must specify ovirt_sdk secrets file as /absolute/path/to/secrets.py" print(err) sys.exit(1) sys.path.append(os.path.dirname(secrets_path)) try: # noinspection PyUnresolvedReferences,PyPackageRequirements import secrets kwargs = getattr(secrets, 'OVIRT_KEYWORD_PARAMS', {}) except ImportError: pass if not secrets_found: kwargs = { 'url': config.get('ovirt', 'ovirt_url'), 'username': config.get('ovirt', 'ovirt_username'), 'password': config.get('ovirt', 'ovirt_password'), } # If the appropriate environment variables are set, they override # other configuration; process those into our args and kwargs. kwargs['url'] = os.environ.get('OVIRT_URL', kwargs['url']) kwargs['username'] = next(val for val in [os.environ.get('OVIRT_EMAIL'), os.environ.get('OVIRT_USERNAME'), kwargs['username']] if val is not None) kwargs['password'] = next(val for val in [os.environ.get('OVIRT_PASS'), os.environ.get('OVIRT_PASSWORD'), kwargs['password']] if val is not None) # Retrieve and return the ovirt driver. return API(insecure=True, **kwargs) @staticmethod def parse_cli_args(): """ Command line argument processing :rtype : argparse.Namespace """ parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on ovirt') parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)') parser.add_argument('--host', action='store', help='Get all information about an instance') parser.add_argument('--pretty', action='store_true', default=False, help='Pretty format (default: False)') return parser.parse_args() def node_to_dict(self, inst): """ :type inst: params.VM """ if inst is None: return {} inst.get_custom_properties() ips = [ip.get_address() for ip in inst.get_guest_info().get_ips().get_ip()] \ if inst.get_guest_info() is not None else [] stats = {} for stat in inst.get_statistics().list(): stats[stat.get_name()] = stat.get_values().get_value()[0].get_datum() return { 'ovirt_uuid': inst.get_id(), 'ovirt_id': inst.get_id(), 'ovirt_image': inst.get_os().get_type(), 'ovirt_machine_type': inst.get_instance_type(), 'ovirt_ips': ips, 'ovirt_name': inst.get_name(), 'ovirt_description': inst.get_description(), 'ovirt_status': inst.get_status().get_state(), 'ovirt_zone': inst.get_cluster().get_id(), 'ovirt_tags': self.get_tags(inst), 'ovirt_stats': stats, # Hosts don't have a public name, so we add an IP 'ansible_ssh_host': ips[0] if len(ips) > 0 else None } @staticmethod def get_tags(inst): """ :type inst: params.VM """ return [x.get_name() for x in inst.get_tags().list()] # noinspection PyBroadException,PyUnusedLocal def get_instance(self, instance_name): """Gets details about a specific instance """ try: return self.driver.vms.get(name=instance_name) except Exception as e: return None def group_instances(self): """Group all instances""" groups = defaultdict(list) meta = {"hostvars": {}} for node in self.driver.vms.list(): assert isinstance(node, params.VM) name = node.get_name() meta["hostvars"][name] = self.node_to_dict(node) zone = node.get_cluster().get_name() groups[zone].append(name) tags = self.get_tags(node) for t in tags: tag = 'tag_%s' % t groups[tag].append(name) nets = [x.get_name() for x in node.get_nics().list()] for net in nets: net = 'network_%s' % net groups[net].append(name) status = node.get_status().get_state() stat = 'status_%s' % status.lower() if stat in groups: groups[stat].append(name) else: groups[stat] = [name] groups["_meta"] = meta return groups @staticmethod def json_format_dict(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) # Run the script OVirtInventory()